feat(editor): configure SQL variable syntax

This commit is contained in:
miracle 2026-07-11 20:45:07 +08:00 committed by GitHub
parent 54632f4c16
commit 1a309b6b63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 450 additions and 24 deletions

View File

@ -239,16 +239,32 @@ function promptActiveDatabaseSelection() {
toast(t("editor.selectDatabaseRequired"), 2500);
}
const { dangerSql, pendingDangerSql, showDangerDialog, suppressDangerConfirm, tryExecute, doExecute, cancelActiveExecution, tryExplain, onDangerConfirm, showSqlParameterDialog, sqlParameterSourceSql, sqlParameterNames, sqlParameterDatabaseType, onSqlParametersConfirm, explainMode } =
useSqlExecution({
activeTab,
activeConnection,
executableSql,
resolveExecutableSql: resolveActiveExecutableSql,
activeOutputView,
blockDangerousRedisCommands,
onMissingDatabase: promptActiveDatabaseSelection,
});
const {
dangerSql,
pendingDangerSql,
showDangerDialog,
suppressDangerConfirm,
tryExecute,
doExecute,
cancelActiveExecution,
tryExplain,
onDangerConfirm,
showSqlParameterDialog,
sqlParameterSourceSql,
sqlParameterNames,
sqlParameterDatabaseType,
sqlParameterEnabledSyntaxes,
onSqlParametersConfirm,
explainMode,
} = useSqlExecution({
activeTab,
activeConnection,
executableSql,
resolveExecutableSql: resolveActiveExecutableSql,
activeOutputView,
blockDangerousRedisCommands,
onMissingDatabase: promptActiveDatabaseSelection,
});
function requestActiveEditorExecute() {
if (contentAreaRef.value?.requestQueryEditorExecute?.()) return;
@ -2007,6 +2023,7 @@ onUnmounted(() => {
:sql-parameter-source-sql="sqlParameterSourceSql"
:sql-parameter-names="sqlParameterNames"
:sql-parameter-database-type="sqlParameterDatabaseType"
:sql-parameter-enabled-syntaxes="sqlParameterEnabledSyntaxes"
@update:show-connection-dialog="setConnectionDialogOpen"
@update:show-danger-dialog="showDangerDialog = $event"
@update:suppress-danger-confirm="suppressDangerConfirm = $event"

View File

@ -88,6 +88,7 @@ import { normalizeSqlFormatterSettings, type SqlFormatterSettings } from "@/lib/
import { currentExecutableStatementRange, type SqlTextRange } from "@/lib/sql/sqlStatementRanges";
import { executableStatementRangeCacheForDoc, executableStatementRangeStartingAt, type ExecutableStatementRangeCache } from "@/lib/sql/executableStatementRangeCache";
import { EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE, parseTableColumnTemplateFields, TABLE_COLUMN_TEMPLATE_DATABASE_TYPES } from "@/lib/table/tableColumnTemplates";
import { DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES, normalizeSqlVariableSyntaxOverrides, SQL_VARIABLE_SYNTAX_DATABASE_TYPES, SQL_VARIABLE_SYNTAX_KEYS, SQL_VARIABLE_SYNTAX_TOKENS, type SqlVariableSyntaxOverrides, type SqlVariableSyntaxToggles } from "@/lib/sql/sqlVariableSyntax";
import { buildMcpCodexConfig, buildMcpJsonConfig, buildMcpOpenCodeConfig, buildMcpVsCodeConfig, type McpEnvEntry, type McpLaunchConfig } from "@/lib/mcp/mcpConfigTemplates";
import { isWindows } from "@/lib/backend/platform";
import { combineDataTypeForDatabase, dataTypeLengthInputValue, getDataTypeOptions, getDefaultLengthForType, isDataTypeLengthDisabled, splitDataType } from "@/lib/table/tableStructureEditorState";
@ -284,6 +285,33 @@ const editInfiniteScroll = ref(settingsStore.editorSettings.infiniteScroll);
const editInfiniteScrollMaxRows = ref(settingsStore.editorSettings.infiniteScrollMaxRows);
const editTableColumnTemplateRows = ref<TableColumnTemplateGridRow[]>(tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields));
const editTableColumnTemplateDatabaseType = ref<DatabaseType>(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES[0] ?? "mysql");
const editSqlVariableSyntaxOverrides = ref<SqlVariableSyntaxOverrides>(normalizeSqlVariableSyntaxOverrides(settingsStore.editorSettings.sqlVariableSyntaxOverrides));
const editSqlVariableSyntaxDatabaseType = ref<DatabaseType>(SQL_VARIABLE_SYNTAX_DATABASE_TYPES[0] ?? "mysql");
function sqlVariableSyntaxToggle(key: keyof SqlVariableSyntaxToggles): boolean {
return editSqlVariableSyntaxOverrides.value[editSqlVariableSyntaxDatabaseType.value]?.[key] ?? true;
}
function setSqlVariableSyntaxToggle(key: keyof SqlVariableSyntaxToggles, value: boolean) {
const dbType = editSqlVariableSyntaxDatabaseType.value;
const merged: SqlVariableSyntaxToggles = {
...DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES,
...editSqlVariableSyntaxOverrides.value[dbType],
[key]: value,
};
const next: SqlVariableSyntaxOverrides = { ...editSqlVariableSyntaxOverrides.value };
// Keep storage sparse: an all-enabled type has no entry; otherwise persist only the disabled syntaxes.
if (SQL_VARIABLE_SYNTAX_KEYS.every((toggleKey) => merged[toggleKey])) {
delete next[dbType];
} else {
const partial: Partial<SqlVariableSyntaxToggles> = {};
for (const toggleKey of SQL_VARIABLE_SYNTAX_KEYS) {
if (!merged[toggleKey]) partial[toggleKey] = false;
}
next[dbType] = partial;
}
editSqlVariableSyntaxOverrides.value = next;
}
const tableColumnTemplateSectionRef = ref<HTMLElement | null>(null);
const draggedTableColumnTemplateRowId = ref<string | null>(null);
let tableColumnTemplatePointerDragCleanup: (() => void) | null = null;
@ -393,6 +421,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
updateDownloadSource: editUpdateDownloadSource.value,
toolbarItems: { ...editToolbarItems.value },
snippets: editSnippets.value,
sqlVariableSyntaxOverrides: editSqlVariableSyntaxOverrides.value,
};
}
@ -664,6 +693,7 @@ function syncEditorSettingsDraftFromStore() {
editUpdateDownloadSource.value = settingsStore.editorSettings.updateDownloadSource;
editToolbarItems.value = { ...settingsStore.editorSettings.toolbarItems };
editSnippets.value = settingsStore.editorSettings.snippets.map(editableSnippet);
editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(settingsStore.editorSettings.sqlVariableSyntaxOverrides);
editEditorSettingsBase.value = editorSettingsDraftFromSettings(settingsStore.editorSettings);
}
@ -818,6 +848,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled;
editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution;
editConfirmUnsavedSqlClose.value = DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose;
editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(DEFAULT_EDITOR_SETTINGS.sqlVariableSyntaxOverrides);
} else if (tab === "formatter") {
editSqlFormatter.value = normalizeSqlFormatterSettings(DEFAULT_EDITOR_SETTINGS.sqlFormatter);
sqlFormatterConfigValid.value = true;
@ -890,6 +921,7 @@ function resetAllDefaults() {
editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled;
editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution;
editConfirmUnsavedSqlClose.value = DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose;
editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(DEFAULT_EDITOR_SETTINGS.sqlVariableSyntaxOverrides);
editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout;
editShowTrayIcon.value = DEFAULT_DESKTOP_SETTINGS.show_tray_icon;
editQuitOnClose.value = DEFAULT_DESKTOP_SETTINGS.quit_on_close;
@ -2747,6 +2779,39 @@ onUnmounted(cleanupPreviewEditor);
<Separator />
<div class="space-y-3">
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="space-y-1">
<div class="text-sm font-medium text-muted-foreground">{{ t("settings.sqlVariableSyntax") }}</div>
<p class="text-xs text-muted-foreground">{{ 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>
<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 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>
<span>{{ t(`settings.sqlVariableSyntax_${key}`) }}</span>
</Label>
<p class="text-xs text-muted-foreground">{{ 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)" />
</div>
</div>
</div>
<Separator />
<!-- Live Preview -->
<div class="space-y-2">
<Label>{{ t("settings.preview") }}</Label>

View File

@ -25,6 +25,7 @@ const props = defineProps<{
sql: string;
parameters: SqlParameterDescriptor[];
databaseType?: DatabaseType;
enabledSyntaxes?: SqlParameterSyntax[];
}>();
const emit = defineEmits<{
@ -46,7 +47,7 @@ const syntaxLabels: Record<SqlParameterSyntax, string> = {
sqlserver: "@name",
};
const resolvedSql = computed(() => substituteSqlParameters(props.sql, values.value, { databaseType: props.databaseType }));
const resolvedSql = computed(() => substituteSqlParameters(props.sql, values.value, { databaseType: props.databaseType, enabledSyntaxes: props.enabledSyntaxes }));
const highlightedSql = computed(() => highlight(resolvedSql.value));
watch(

View File

@ -20,7 +20,7 @@ const DataGenerateDialog = defineAsyncComponent(() => import("@/components/gener
import { useConnectionStore } from "@/stores/connectionStore";
import { useDialogSources } from "@/composables/useDialogSources";
import type { ConnectionDeepLinkDraft } from "@/lib/connection/connectionDeepLink";
import type { SqlParameterDescriptor } from "@/lib/sql/sqlParameters";
import type { SqlParameterDescriptor, SqlParameterSyntax } from "@/lib/sql/sqlParameters";
import type { ConfigTab } from "@/components/connection/ConnectionDialog.vue";
import type { DatabaseType } from "@/types/database";
@ -35,6 +35,7 @@ const props = defineProps<{
sqlParameterSourceSql: string;
sqlParameterNames: SqlParameterDescriptor[];
sqlParameterDatabaseType?: DatabaseType;
sqlParameterEnabledSyntaxes?: SqlParameterSyntax[];
}>();
const emit = defineEmits<{
@ -135,7 +136,16 @@ watch(
@update:suppress-future-prompts="emit('update:suppressDangerConfirm', $event)"
@confirm="emit('dangerConfirm')"
/>
<SqlParameterDialog v-if="showSqlParameterDialog" :open="showSqlParameterDialog" :sql="sqlParameterSourceSql" :parameters="sqlParameterNames" :database-type="sqlParameterDatabaseType" @update:open="emit('update:showSqlParameterDialog', $event)" @execute="emit('sqlParametersConfirm', $event)" />
<SqlParameterDialog
v-if="showSqlParameterDialog"
:open="showSqlParameterDialog"
:sql="sqlParameterSourceSql"
:parameters="sqlParameterNames"
:database-type="sqlParameterDatabaseType"
:enabled-syntaxes="sqlParameterEnabledSyntaxes"
@update:open="emit('update:showSqlParameterDialog', $event)"
@execute="emit('sqlParametersConfirm', $event)"
/>
<DataTransferDialog v-model:open="dialogs.showTransferDialog.value" :prefill-connection-id="dialogs.transferPrefillConnectionId.value" :prefill-database="dialogs.transferPrefillDatabase.value" />
<SchemaDiffDialog v-if="dialogs.showSchemaDiffDialog.value" v-model:open="dialogs.showSchemaDiffDialog.value" :prefill-connection-id="dialogs.schemaDiffPrefillConnectionId.value" :prefill-database="dialogs.schemaDiffPrefillDatabase.value" :prefill-schema="dialogs.schemaDiffPrefillSchema.value" />
<DataCompareDialog

View File

@ -11,8 +11,9 @@ import { classifySqlActivityKind } from "@/lib/history/historyActivityKind";
import { sqlMetadataRefreshTarget } from "@/lib/sql/sqlMetadataRefresh";
import { classifyRedisCommandSafety, firstRedisCommandToken } from "@/lib/redis/redisCommandSafety";
import { isSqlExecutionSnapshot, resolveExecutableSql, type SqlExecutionOverride, type SqlExecutionSnapshot } from "@/lib/sql/sqlExecutionTarget";
import { extractSqlParameterDescriptors, type SqlParameterDescriptor } from "@/lib/sql/sqlParameters";
import { extractSqlParameterDescriptors, type SqlParameterDescriptor, type SqlParameterSyntax } from "@/lib/sql/sqlParameters";
import { expandSqlVariables } from "@/lib/sql/sqlVariables";
import { enabledSqlParameterSyntaxes, resolveSqlVariableSyntaxToggles } from "@/lib/sql/sqlVariableSyntax";
import type { ConnectionConfig, DatabaseType, QueryTab } from "@/types/database";
const DANGER_RE = /^\s*(DROP|DELETE|TRUNCATE|ALTER|UPDATE|MERGE|REPLACE)\b/i;
@ -63,12 +64,15 @@ export function useSqlExecution(deps: {
const sqlParameterSourceSql = ref("");
const sqlParameterNames = ref<SqlParameterDescriptor[]>([]);
const sqlParameterDatabaseType = ref<DatabaseType | undefined>();
const sqlParameterEnabledSyntaxes = ref<SqlParameterSyntax[]>([]);
async function resolvedExecutableSql(source?: SqlExecutionOverride): Promise<string> {
if (typeof source === "string") return expandSqlVariables(source).sql;
if (deps.resolveExecutableSql) return expandSqlVariables(await deps.resolveExecutableSql(source)).sql;
if (isSqlExecutionSnapshot(source)) return expandSqlVariables(resolveExecutableSql(source.fullSql, source.selectedSql, { cursorPos: source.cursorPos })).sql;
return expandSqlVariables(deps.executableSql.value).sql;
const atSetEnabled = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, deps.activeConnection.value?.db_type).atSet;
const expand = (sql: string) => (atSetEnabled ? expandSqlVariables(sql).sql : sql);
if (typeof source === "string") return expand(source);
if (deps.resolveExecutableSql) return expand(await deps.resolveExecutableSql(source));
if (isSqlExecutionSnapshot(source)) return expand(resolveExecutableSql(source.fullSql, source.selectedSql, { cursorPos: source.cursorPos }));
return expand(deps.executableSql.value);
}
async function tryExecute(sqlOverride?: SqlExecutionOverride) {
@ -110,11 +114,14 @@ export function useSqlExecution(deps: {
function prepareSqlParameterDialog(sql: string): boolean {
const databaseType = deps.activeConnection.value?.db_type;
const parameters = extractSqlParameterDescriptors(sql, { databaseType });
const toggles = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, databaseType);
const enabledSyntaxes = enabledSqlParameterSyntaxes(toggles);
const parameters = extractSqlParameterDescriptors(sql, { databaseType, enabledSyntaxes });
if (!parameters.length) return false;
sqlParameterSourceSql.value = sql;
sqlParameterNames.value = parameters;
sqlParameterDatabaseType.value = databaseType;
sqlParameterEnabledSyntaxes.value = enabledSyntaxes;
showSqlParameterDialog.value = true;
return true;
}
@ -206,6 +213,7 @@ export function useSqlExecution(deps: {
sqlParameterSourceSql.value = "";
sqlParameterNames.value = [];
sqlParameterDatabaseType.value = undefined;
sqlParameterEnabledSyntaxes.value = [];
await continueExecute(sql);
}
@ -214,6 +222,7 @@ export function useSqlExecution(deps: {
sqlParameterSourceSql.value = "";
sqlParameterNames.value = [];
sqlParameterDatabaseType.value = undefined;
sqlParameterEnabledSyntaxes.value = [];
});
return {
@ -230,6 +239,7 @@ export function useSqlExecution(deps: {
sqlParameterSourceSql,
sqlParameterNames,
sqlParameterDatabaseType,
sqlParameterEnabledSyntaxes,
onSqlParametersConfirm,
explainMode,
};

View File

@ -3164,6 +3164,20 @@ export default {
confirmDangerousSqlExecutionDescription: "When disabled, ALTER, DROP, DELETE, TRUNCATE, and other dangerous SQL run without the warning dialog.",
confirmUnsavedSqlClose: "Confirm before closing unsaved SQL",
confirmUnsavedSqlCloseDescription: "When disabled, SQL tabs with unsaved edits close or quit without the save confirmation dialog.",
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.",
sqlVariableSyntax_positional: "Positional placeholder",
sqlVariableSyntax_positionalDescription: "Prompt for a value to substitute each positional placeholder in order.",
sqlVariableSyntax_named: "Named placeholder",
sqlVariableSyntax_namedDescription: "Prompt for a value to substitute named placeholders.",
sqlVariableSyntax_shell: "Shell-style placeholder",
sqlVariableSyntax_shellDescription: "Prompt for a value to substitute shell-style placeholders.",
sqlVariableSyntax_mybatis: "MyBatis-style placeholder",
sqlVariableSyntax_mybatisDescription: "Prompt for a value to substitute MyBatis-style placeholders.",
sqlVariableSyntax_sqlserver: "SQL Server-style placeholder",
sqlVariableSyntax_sqlserverDescription: "Prompt for a value to substitute SQL Server-style placeholders (may collide with session variables).",
sqlVariableSyntax_atSet: "Inline variable expansion",
sqlVariableSyntax_atSetDescription: "Expand inline variable declarations and substitute their values before running.",
autoAliasTables: "Automatically add table aliases",
autoAliasTablesDescription: "When selecting a table completion in FROM or JOIN, insert a generated alias such as order_items AS oi.",
redisScanPageSize: "Redis scan count",

View File

@ -3045,6 +3045,20 @@ export default withEnglishFallback({
confirmDangerousSqlExecutionDescription: "Cuando se desactiva, ALTER, DROP, DELETE, TRUNCATE y otras sentencias peligrosas se ejecutan sin el diálogo de advertencia.",
confirmUnsavedSqlClose: "Confirmar antes de cerrar SQL sin guardar",
confirmUnsavedSqlCloseDescription: "Cuando se desactiva, las pestañas SQL con ediciones sin guardar se cierran o salen sin el diálogo de confirmación de guardado.",
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.",
sqlVariableSyntax_positional: "Marcador posicional",
sqlVariableSyntax_positionalDescription: "Solicita un valor para sustituir cada marcador posicional en orden.",
sqlVariableSyntax_named: "Marcador con nombre",
sqlVariableSyntax_namedDescription: "Solicita un valor para sustituir los marcadores con nombre.",
sqlVariableSyntax_shell: "Marcador estilo shell",
sqlVariableSyntax_shellDescription: "Solicita un valor para sustituir los marcadores de estilo shell.",
sqlVariableSyntax_mybatis: "Marcador estilo MyBatis",
sqlVariableSyntax_mybatisDescription: "Solicita un valor para sustituir los marcadores de estilo MyBatis.",
sqlVariableSyntax_sqlserver: "Marcador estilo SQL Server",
sqlVariableSyntax_sqlserverDescription: "Solicita un valor para sustituir los marcadores de estilo SQL Server (pueden entrar en conflicto con variables de sesión).",
sqlVariableSyntax_atSet: "Expansión de variables en línea",
sqlVariableSyntax_atSetDescription: "Expande las declaraciones de variables en línea y sustituye sus valores antes de ejecutar.",
autoAliasTables: "Agregar alias de tabla automáticamente",
autoAliasTablesDescription: "Al seleccionar una tabla en FROM o JOIN, inserta un alias generado como order_items AS oi.",
redisScanPageSize: "Cantidad de escaneo Redis",

View File

@ -3043,6 +3043,20 @@ export default withEnglishFallback({
confirmDangerousSqlExecutionDescription: "Se disattivato, ALTER, DROP, DELETE, TRUNCATE e altri SQL pericolosi verranno eseguiti senza la finestra di avviso.",
confirmUnsavedSqlClose: "Conferma prima di chiudere SQL non salvato",
confirmUnsavedSqlCloseDescription: "Se disattivato, le schede SQL con modifiche non salvate verranno chiuse o usciranno senza la finestra di conferma di salvataggio.",
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.",
sqlVariableSyntax_positional: "Segnaposto posizionale",
sqlVariableSyntax_positionalDescription: "Richiede un valore per sostituire ogni segnaposto posizionale in ordine.",
sqlVariableSyntax_named: "Segnaposto con nome",
sqlVariableSyntax_namedDescription: "Richiede un valore per sostituire i segnaposto con nome.",
sqlVariableSyntax_shell: "Segnaposto stile shell",
sqlVariableSyntax_shellDescription: "Richiede un valore per sostituire i segnaposto in stile shell.",
sqlVariableSyntax_mybatis: "Segnaposto stile MyBatis",
sqlVariableSyntax_mybatisDescription: "Richiede un valore per sostituire i segnaposto in stile MyBatis.",
sqlVariableSyntax_sqlserver: "Segnaposto stile SQL Server",
sqlVariableSyntax_sqlserverDescription: "Richiede un valore per sostituire i segnaposto in stile SQL Server (possono entrare in conflitto con le variabili di sessione).",
sqlVariableSyntax_atSet: "Espansione di variabili inline",
sqlVariableSyntax_atSetDescription: "Espande le dichiarazioni di variabili inline e ne sostituisce i valori prima dell'esecuzione.",
autoAliasTables: "Aggiungi alias tabella automaticamente",
autoAliasTablesDescription: "Quando scegli una tabella in FROM o JOIN, inserisce un alias generato come order_items AS oi.",
redisScanPageSize: "Conteggio scansione Redis",

View File

@ -3035,6 +3035,20 @@ export default withEnglishFallback({
confirmDangerousSqlExecutionDescription: "無効時、ALTER、DROP、DELETE、TRUNCATEなどの危険なSQLが警告ダイアログなしで実行されます。",
confirmUnsavedSqlClose: "未保存のSQLを閉じる前に確認",
confirmUnsavedSqlCloseDescription: "無効時、未保存の編集があるSQLタブは保存確認ダイアログなしで閉じるか終了します。",
sqlVariableSyntax: "SQL 変数・プレースホルダー置換",
sqlVariableSyntaxDescription: "SQL 実行前に置換する変数・プレースホルダー構文をデータベース種別ごとに選択します。既定ではすべて有効です。",
sqlVariableSyntax_positional: "位置プレースホルダー",
sqlVariableSyntax_positionalDescription: "各位置プレースホルダーを順番に置き換える値の入力を求めます。",
sqlVariableSyntax_named: "名前付きプレースホルダー",
sqlVariableSyntax_namedDescription: "名前付きプレースホルダーを置き換える値の入力を求めます。",
sqlVariableSyntax_shell: "シェル形式プレースホルダー",
sqlVariableSyntax_shellDescription: "シェル形式プレースホルダーを置き換える値の入力を求めます。",
sqlVariableSyntax_mybatis: "MyBatis 形式プレースホルダー",
sqlVariableSyntax_mybatisDescription: "MyBatis 形式プレースホルダーを置き換える値の入力を求めます。",
sqlVariableSyntax_sqlserver: "SQL Server 形式プレースホルダー",
sqlVariableSyntax_sqlserverDescription: "SQL Server 形式プレースホルダーを置き換える値の入力を求めます(セッション変数と競合する場合があります)。",
sqlVariableSyntax_atSet: "インライン変数展開",
sqlVariableSyntax_atSetDescription: "実行前にインライン変数宣言を展開し、その値をインライン化します。",
autoAliasTables: "テーブル別名を自動追加",
autoAliasTablesDescription: "FROM または JOIN でテーブル補完を選択すると、order_items AS oi のような生成済み別名を挿入します。",
redisScanPageSize: "Redisスキャンカウント",

View File

@ -3045,6 +3045,20 @@ export default withEnglishFallback({
confirmDangerousSqlExecutionDescription: "Quando desativado, ALTER, DROP, DELETE, TRUNCATE e outros SQL perigosos são executados sem a caixa de diálogo de aviso.",
confirmUnsavedSqlClose: "Confirmar antes de fechar SQL não salvo",
confirmUnsavedSqlCloseDescription: "Quando desativado, abas SQL com edições não salvas são fechadas ou o app é encerrado sem a caixa de diálogo de confirmação de salvamento.",
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.",
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",
sqlVariableSyntax_namedDescription: "Solicita um valor para substituir os espaços reservados nomeados.",
sqlVariableSyntax_shell: "Espaço reservado estilo shell",
sqlVariableSyntax_shellDescription: "Solicita um valor para substituir os espaços reservados de estilo shell.",
sqlVariableSyntax_mybatis: "Espaço reservado estilo MyBatis",
sqlVariableSyntax_mybatisDescription: "Solicita um valor para substituir os espaços reservados de estilo MyBatis.",
sqlVariableSyntax_sqlserver: "Espaço reservado estilo SQL Server",
sqlVariableSyntax_sqlserverDescription: "Solicita um valor para substituir os espaços reservados de estilo SQL Server (podem colidir com variáveis de sessão).",
sqlVariableSyntax_atSet: "Expansão de variáveis inline",
sqlVariableSyntax_atSetDescription: "Expande as declarações de variáveis inline e substitui seus valores antes de executar.",
autoAliasTables: "Adicionar alias de tabela automaticamente",
autoAliasTablesDescription: "Ao escolher uma tabela em FROM ou JOIN, insere um alias gerado como order_items AS oi.",
redisScanPageSize: "Contagem de scan do Redis",

View File

@ -3163,6 +3163,20 @@ export default withEnglishFallback({
confirmDangerousSqlExecutionDescription: "关闭后ALTER、DROP、DELETE、TRUNCATE 等危险 SQL 将直接执行。",
confirmUnsavedSqlClose: "关闭未保存 SQL 前弹出确认",
confirmUnsavedSqlCloseDescription: "关闭后,有未保存内容的 SQL 标签页会直接关闭或退出,不再弹出保存确认。",
sqlVariableSyntax: "SQL 变量与占位符替换",
sqlVariableSyntaxDescription: "按数据库类型选择执行 SQL 前替换哪些变量与占位符语法,默认全部开启。",
sqlVariableSyntax_positional: "位置占位符",
sqlVariableSyntax_positionalDescription: "按顺序为每个位置占位符提示输入替换值。",
sqlVariableSyntax_named: "命名占位符",
sqlVariableSyntax_namedDescription: "为命名占位符提示输入替换值。",
sqlVariableSyntax_shell: "Shell 风格占位符",
sqlVariableSyntax_shellDescription: "为 Shell 风格占位符提示输入替换值。",
sqlVariableSyntax_mybatis: "MyBatis 风格占位符",
sqlVariableSyntax_mybatisDescription: "为 MyBatis 风格占位符提示输入替换值。",
sqlVariableSyntax_sqlserver: "SQL Server 风格占位符",
sqlVariableSyntax_sqlserverDescription: "为 SQL Server 风格占位符提示输入替换值(可能与会话变量冲突)。",
sqlVariableSyntax_atSet: "内联变量展开",
sqlVariableSyntax_atSetDescription: "执行前展开内联变量声明并就地代入其值。",
autoAliasTables: "自动添加表别名",
autoAliasTablesDescription: "在 FROM 或 JOIN 中选择表名补全时,自动插入类似 order_items AS oi 的表别名。",
redisScanPageSize: "Redis 扫描数量",

View File

@ -2894,6 +2894,20 @@ export default withEnglishFallback({
confirmDangerousSqlExecutionDescription: "關閉後ALTER、DROP、DELETE、TRUNCATE 等危險 SQL 會直接執行。",
confirmUnsavedSqlClose: "關閉未儲存 SQL 前彈出確認",
confirmUnsavedSqlCloseDescription: "關閉後,有未儲存內容的 SQL 分頁會直接關閉或結束,不再彈出儲存確認。",
sqlVariableSyntax: "SQL 變數與佔位符替換",
sqlVariableSyntaxDescription: "依資料庫類型選擇執行 SQL 前替換哪些變數與佔位符語法,預設全部開啟。",
sqlVariableSyntax_positional: "位置佔位符",
sqlVariableSyntax_positionalDescription: "依序為每個位置佔位符提示輸入替換值。",
sqlVariableSyntax_named: "具名佔位符",
sqlVariableSyntax_namedDescription: "為具名佔位符提示輸入替換值。",
sqlVariableSyntax_shell: "Shell 風格佔位符",
sqlVariableSyntax_shellDescription: "為 Shell 風格佔位符提示輸入替換值。",
sqlVariableSyntax_mybatis: "MyBatis 風格佔位符",
sqlVariableSyntax_mybatisDescription: "為 MyBatis 風格佔位符提示輸入替換值。",
sqlVariableSyntax_sqlserver: "SQL Server 風格佔位符",
sqlVariableSyntax_sqlserverDescription: "為 SQL Server 風格佔位符提示輸入替換值(可能與工作階段變數衝突)。",
sqlVariableSyntax_atSet: "內聯變數展開",
sqlVariableSyntax_atSetDescription: "執行前展開內聯變數宣告並就地代入其值。",
autoAliasTables: "自動加入資料表別名",
autoAliasTablesDescription: "在 FROM 或 JOIN 中選擇資料表補全時,自動插入類似 order_items AS oi 的資料表別名。",
redisScanPageSize: "Redis 掃描數量",

View File

@ -276,6 +276,45 @@ DEALLOCATE PREPARE stmt;`;
});
});
describe("enabledSyntaxes option", () => {
const mixedSql = "select ? as a, :named as b, ${shell_name} as c, #{mybatis_name} as d, @sql_server_name as e";
it("extracts every syntax when the option is omitted (backward compatible)", () => {
expect(extractSqlParameters(mixedSql)).toEqual(["?1", "named", "shell_name", "mybatis_name", "sql_server_name"]);
});
it("only extracts the enabled syntaxes", () => {
expect(extractSqlParameters(mixedSql, { enabledSyntaxes: ["named"] })).toEqual(["named"]);
expect(extractSqlParameters(mixedSql, { enabledSyntaxes: ["shell", "mybatis"] })).toEqual(["shell_name", "mybatis_name"]);
});
it("extracts nothing when no syntax is enabled", () => {
expect(extractSqlParameters(mixedSql, { enabledSyntaxes: [] })).toEqual([]);
});
it("leaves disabled-syntax tokens untouched when substituting", () => {
// Only :named is enabled, so every other token survives verbatim.
expect(substituteSqlParameters(mixedSql, { named: { kind: "number", value: "2" } }, { enabledSyntaxes: ["named"] })).toBe("select ? as a, 2 as b, ${shell_name} as c, #{mybatis_name} as d, @sql_server_name as e");
});
it("does not consume the positional counter for disabled positional placeholders", () => {
expect(substituteSqlParameters("select ?, ?", {}, { enabledSyntaxes: ["named"] })).toBe("select ?, ?");
});
it("keeps #{name} out of hash-comment handling when mybatis is disabled", () => {
expect(extractSqlParameters("select #{mybatis_name} from t", { enabledSyntaxes: ["shell"] })).toEqual([]);
expect(substituteSqlParameters("select #{mybatis_name} from t", {}, { enabledSyntaxes: ["shell"] })).toBe("select #{mybatis_name} from t");
});
it("intersects the enabled set with the saphana named-parameter rule", () => {
const sql = "select :named as a, ${shell_name} as b";
// saphana already disables :name; enabling named cannot re-enable it.
expect(extractSqlParameters(sql, { databaseType: "saphana", enabledSyntaxes: ["named", "shell"] })).toEqual(["shell_name"]);
// A non-saphana database with named disabled also drops :name.
expect(extractSqlParameters(sql, { enabledSyntaxes: ["shell"] })).toEqual(["shell_name"]);
});
});
describe("sqlParameterLiteral", () => {
it("falls back to quoted strings for invalid boolean input", () => {
expect(sqlParameterLiteral({ kind: "boolean", value: "maybe" })).toBe("'maybe'");

View File

@ -0,0 +1,83 @@
import { describe, expect, it } from "vitest";
import { reactive } from "vue";
import { DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES, enabledSqlParameterSyntaxes, normalizeSqlVariableSyntaxOverrides, resolveSqlVariableSyntaxToggles } from "@/lib/sql/sqlVariableSyntax";
describe("resolveSqlVariableSyntaxToggles", () => {
it("enables every syntax when there are no overrides", () => {
expect(resolveSqlVariableSyntaxToggles(undefined, "mysql")).toEqual(DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES);
expect(resolveSqlVariableSyntaxToggles({}, "mysql")).toEqual(DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES);
});
it("enables every syntax for a database type without an entry", () => {
expect(resolveSqlVariableSyntaxToggles({ mysql: { shell: false } }, "postgres")).toEqual(DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES);
});
it("enables every syntax when the database type is unknown", () => {
expect(resolveSqlVariableSyntaxToggles({ mysql: { shell: false } }, undefined)).toEqual(DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES);
});
it("applies only the disabled syntaxes for the matching database type", () => {
expect(resolveSqlVariableSyntaxToggles({ mysql: { sqlserver: false, atSet: false } }, "mysql")).toEqual({
positional: true,
named: true,
shell: true,
mybatis: true,
sqlserver: false,
atSet: false,
});
});
});
describe("enabledSqlParameterSyntaxes", () => {
it("returns the five placeholder syntaxes and never atSet", () => {
expect(enabledSqlParameterSyntaxes(DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES)).toEqual(["positional", "named", "shell", "mybatis", "sqlserver"]);
});
it("filters out disabled placeholder syntaxes", () => {
expect(enabledSqlParameterSyntaxes({ positional: false, named: true, shell: false, mybatis: true, sqlserver: false, atSet: true })).toEqual(["named", "mybatis"]);
});
it("ignores the atSet toggle entirely", () => {
expect(enabledSqlParameterSyntaxes({ positional: true, named: true, shell: true, mybatis: true, sqlserver: true, atSet: false })).toEqual(["positional", "named", "shell", "mybatis", "sqlserver"]);
expect(enabledSqlParameterSyntaxes({ positional: false, named: false, shell: false, mybatis: false, sqlserver: false, atSet: true })).toEqual([]);
});
});
describe("normalizeSqlVariableSyntaxOverrides", () => {
it("returns an empty object for non-object input", () => {
expect(normalizeSqlVariableSyntaxOverrides(undefined)).toEqual({});
expect(normalizeSqlVariableSyntaxOverrides(null)).toEqual({});
expect(normalizeSqlVariableSyntaxOverrides([])).toEqual({});
expect(normalizeSqlVariableSyntaxOverrides("mysql")).toEqual({});
});
it("keeps only syntaxes explicitly set to false", () => {
expect(normalizeSqlVariableSyntaxOverrides({ mysql: { positional: true, shell: false, atSet: false } })).toEqual({ mysql: { shell: false, atSet: false } });
});
it("drops entries with no disabled syntaxes", () => {
expect(normalizeSqlVariableSyntaxOverrides({ mysql: { positional: true }, postgres: {} })).toEqual({});
});
it("ignores non-boolean values and unknown keys", () => {
expect(normalizeSqlVariableSyntaxOverrides({ mysql: { shell: "false", named: 0, bogus: false, sqlserver: false } })).toEqual({ mysql: { sqlserver: false } });
});
it("skips non-object database entries", () => {
expect(normalizeSqlVariableSyntaxOverrides({ mysql: null, postgres: [], sqlserver: { shell: false } })).toEqual({ sqlserver: { shell: false } });
});
it("is stable across a round-trip", () => {
const normalized = normalizeSqlVariableSyntaxOverrides({ mysql: { shell: false }, oracle: { atSet: false, named: false } });
expect(normalizeSqlVariableSyntaxOverrides(normalized)).toEqual(normalized);
});
it("reads through a Vue reactive proxy without throwing (settings store is reactive; structuredClone would throw DataCloneError here)", () => {
const overrides = reactive({ mysql: { shell: false, atSet: false } });
const cloned = normalizeSqlVariableSyntaxOverrides(overrides);
expect(cloned).toEqual({ mysql: { shell: false, atSet: false } });
// Detached from the reactive source: mutating the clone must not touch the proxy.
cloned.mysql = {};
expect(overrides.mysql).toEqual({ shell: false, atSet: false });
});
});

View File

@ -47,6 +47,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"updateDownloadSource",
"toolbarItems",
"snippets",
"sqlVariableSyntaxOverrides",
] as const satisfies readonly (keyof EditorSettings)[];
export type EditorSettingsDraftKey = (typeof EDITOR_SETTINGS_DRAFT_KEYS)[number];

View File

@ -23,6 +23,8 @@ interface ParameterOccurrence extends SqlParameterDescriptor {
export interface SqlParameterOptions {
databaseType?: DatabaseType;
// Which placeholder syntaxes are recognized. Undefined enables all of them.
enabledSyntaxes?: readonly SqlParameterSyntax[];
}
const PARAMETER_NAME_RE = /^[\p{L}_][\p{L}\p{N}_]*$/u;
@ -78,6 +80,8 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
const occurrences: ParameterOccurrence[] = [];
const nativeSqlServerParameters = collectNativeSqlServerParameters(sql);
const supportsNamedParameters = options?.databaseType !== "saphana";
const enabledSyntaxes = options?.enabledSyntaxes ? new Set(options.enabledSyntaxes) : null;
const isSyntaxEnabled = (syntax: SqlParameterSyntax) => !enabledSyntaxes || enabledSyntaxes.has(syntax);
let i = 0;
let dollarQuoteEnd = "";
let positionalIndex = 0;
@ -110,14 +114,14 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
i = skipBlockComment(sql, i + 2);
continue;
}
if (ch === "?") {
if (ch === "?" && isSyntaxEnabled("positional")) {
positionalIndex += 1;
const key = `?${positionalIndex}`;
occurrences.push({ key, name: key, syntax: "positional", token: "?", start: i, end: i + 1 });
i += 1;
continue;
}
if (ch === ":" && supportsNamedParameters) {
if (ch === ":" && supportsNamedParameters && isSyntaxEnabled("named")) {
const name = readParameterName(sql, i + 1);
if (name && sql[i - 1] !== ":" && sql[i + 1] !== "=") {
occurrences.push({
@ -132,7 +136,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
continue;
}
}
if (ch === "$" && next === "{") {
if (ch === "$" && next === "{" && isSyntaxEnabled("shell")) {
const end = sql.indexOf("}", i + 2);
if (end !== -1) {
const name = sql.slice(i + 2, end).trim();
@ -143,7 +147,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
}
}
}
if (ch === "#" && next === "{") {
if (ch === "#" && next === "{" && isSyntaxEnabled("mybatis")) {
const end = sql.indexOf("}", i + 2);
if (end !== -1) {
const name = sql.slice(i + 2, end).trim();
@ -158,7 +162,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
i = skipLine(sql, i + 1);
continue;
}
if (ch === "@") {
if (ch === "@" && isSyntaxEnabled("sqlserver")) {
const name = readParameterName(sql, i + 1);
if (name && next !== "@" && sql[i - 1] !== "@" && !nativeSqlServerParameters.declared.has(name.toLowerCase()) && !nativeSqlServerParameters.ignoredStarts.has(i)) {
occurrences.push({

View File

@ -0,0 +1,93 @@
// Per-database-type configuration for SQL variable/placeholder substitution.
//
// DBX runs two client-side substitution systems before sending SQL to a backend:
// the placeholder parameter dialog (`sqlParameters.ts`, five syntaxes) and the
// `@set name = value;` expansion (`sqlVariables.ts`). This module lets users opt
// out of individual syntaxes per database type. Every toggle defaults to `true`,
// so an empty/absent config reproduces the historical "always substitute" behaviour.
//
// Storage is sparse: only syntaxes explicitly turned off (`false`) are persisted,
// keyed by database type. Anything not stored resolves to enabled.
import type { DatabaseType } from "@/types/database";
import type { SqlParameterSyntax } from "@/lib/sql/sqlParameters";
import { manifestDatabaseTypes } from "@/lib/database/databaseDriverManifest";
export interface SqlVariableSyntaxToggles {
positional: boolean; // ?
named: boolean; // :name
shell: boolean; // ${name}
mybatis: boolean; // #{name}
sqlserver: boolean; // @name
atSet: boolean; // @set name = value; (expandSqlVariables)
}
export const DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES: SqlVariableSyntaxToggles = {
positional: true,
named: true,
shell: true,
mybatis: true,
sqlserver: true,
atSet: true,
};
// Fixed order for iterating the toggles in the settings UI.
export const SQL_VARIABLE_SYNTAX_KEYS = ["positional", "named", "shell", "mybatis", "sqlserver", "atSet"] as const satisfies readonly (keyof SqlVariableSyntaxToggles)[];
// Display tokens (code symbols, not translated) shown next to each toggle.
export const SQL_VARIABLE_SYNTAX_TOKENS: Record<keyof SqlVariableSyntaxToggles, string> = {
positional: "?",
named: ":name",
shell: "${name}",
mybatis: "#{name}",
sqlserver: "@name",
atSet: "@set …;",
};
// The first five toggles map one-to-one onto placeholder parameter syntaxes.
const PARAMETER_SYNTAX_KEYS = ["positional", "named", "shell", "mybatis", "sqlserver"] as const satisfies readonly SqlParameterSyntax[];
export type SqlVariableSyntaxOverrides = Partial<Record<DatabaseType, Partial<SqlVariableSyntaxToggles>>>;
// Database types offered in the settings selector — every connectable type.
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.
*/
export function resolveSqlVariableSyntaxToggles(overrides: SqlVariableSyntaxOverrides | undefined, dbType: DatabaseType | undefined): SqlVariableSyntaxToggles {
const partial = dbType ? overrides?.[dbType] : undefined;
return {
positional: partial?.positional ?? true,
named: partial?.named ?? true,
shell: partial?.shell ?? true,
mybatis: partial?.mybatis ?? true,
sqlserver: partial?.sqlserver ?? true,
atSet: partial?.atSet ?? true,
};
}
/** Derive the enabled placeholder parameter syntaxes (excludes `atSet`). */
export function enabledSqlParameterSyntaxes(toggles: SqlVariableSyntaxToggles): SqlParameterSyntax[] {
return PARAMETER_SYNTAX_KEYS.filter((key) => toggles[key]);
}
/**
* Normalize persisted overrides into a sparse structure: drop non-object entries
* and only keep syntaxes explicitly set to `false`. Absence resolves to enabled.
*/
export function normalizeSqlVariableSyntaxOverrides(value: unknown): SqlVariableSyntaxOverrides {
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
const result: SqlVariableSyntaxOverrides = {};
for (const [dbType, raw] of Object.entries(value as Record<string, unknown>)) {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
const partial: Partial<SqlVariableSyntaxToggles> = {};
for (const key of SQL_VARIABLE_SYNTAX_KEYS) {
if ((raw as Record<string, unknown>)[key] === false) partial[key] = false;
}
if (Object.keys(partial).length > 0) result[dbType as DatabaseType] = partial;
}
return result;
}

View File

@ -6,6 +6,7 @@ import { normalizeShortcutSettings, type ShortcutSettings } from "@/lib/editor/s
import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebar/sidebarTableNameDisplay";
import { DEFAULT_SQL_FORMATTER_SETTINGS, normalizeSqlFormatterSettings, type SqlFormatterSettings } from "@/lib/sql/sqlFormatterConfig";
import { normalizeSqlVariableSyntaxOverrides, type SqlVariableSyntaxOverrides } from "@/lib/sql/sqlVariableSyntax";
import type { SidebarActivation } from "@/lib/sidebar/treeNodeClick";
import type { SqlSnippet } from "@/types/database";
import { DEFAULT_SQL_SNIPPETS } from "@/lib/sql/sqlCompletion";
@ -426,6 +427,7 @@ export interface EditorSettings {
toolbarItems: ToolbarItems;
objectBrowserShowCheckbox: boolean;
objectBrowserViewMode: "list" | "grid";
sqlVariableSyntaxOverrides: SqlVariableSyntaxOverrides;
}
export interface ToolbarItems {
@ -558,6 +560,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
toolbarItems: { ...DEFAULT_TOOLBAR_ITEMS },
objectBrowserShowCheckbox: false,
objectBrowserViewMode: "list",
sqlVariableSyntaxOverrides: {},
};
export const STORAGE_KEY = "dbx-editor-settings";
@ -790,6 +793,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,
sqlVariableSyntaxOverrides: normalizeSqlVariableSyntaxOverrides(settings.sqlVariableSyntaxOverrides),
};
}
@ -1034,6 +1038,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.sqlVariableSyntaxOverrides !== undefined) editorSettings.value.sqlVariableSyntaxOverrides = normalizeSqlVariableSyntaxOverrides(partial.sqlVariableSyntaxOverrides);
saveEditorSettings(editorSettings.value);
}