Fix:修复 SQL 子查询语义诊断误报 (#2184)

* Fix:修复 SQL 子查询语义诊断误报

* Improve:添加 SQL 语义诊断开关

* Improve:优化 SQL 语义诊断开关说明

* Fix:收紧 SQL 语义诊断飘红范围

* Improve:调整 SQL 语义诊断默认策略

---------

Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
zipgandstaff authored and GitHub committed 2026-06-30 20:15:59 +08:00
1 parent 73a996ebe6
commit c38bc05417
12 files changed
+417 -31

No files matched your search

@@ -34,6 +34,7 @@ import {
type DesktopIconTheme,
type InterfaceLayout,
type DisconnectTabHandlingMode,
type SqlSemanticDiagnosticsMode,
type UpdateDownloadSource,
type CustomThemeColors,
type CustomTheme,
@@ -190,6 +191,8 @@ const editExecuteMode = ref(settingsStore.editorSettings.executeMode);
const editShowExecutionTargetPicker = ref(settingsStore.editorSettings.showExecutionTargetPicker);
const editAutoAliasTables = ref(settingsStore.editorSettings.autoAliasTables);
const editWordWrap = ref(settingsStore.editorSettings.wordWrap);
const editSqlSemanticDiagnosticsMode = ref<SqlSemanticDiagnosticsMode>(settingsStore.editorSettings.sqlSemanticDiagnosticsMode);
const editSqlSemanticDiagnosticsEnabled = ref(settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled);
const editConfirmDangerousSqlExecution = ref(settingsStore.editorSettings.confirmDangerousSqlExecution);
const editAppLayout = ref(settingsStore.editorSettings.appLayout);
const editShowTrayIcon = ref(settingsStore.desktopSettings.show_tray_icon);
@@ -468,6 +471,8 @@ watch(
editShowExecutionTargetPicker.value = settingsStore.editorSettings.showExecutionTargetPicker;
editAutoAliasTables.value = settingsStore.editorSettings.autoAliasTables;
editWordWrap.value = settingsStore.editorSettings.wordWrap;
editSqlSemanticDiagnosticsMode.value = settingsStore.editorSettings.sqlSemanticDiagnosticsMode;
editSqlSemanticDiagnosticsEnabled.value = settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled;
editConfirmDangerousSqlExecution.value = settingsStore.editorSettings.confirmDangerousSqlExecution;
editAppLayout.value = settingsStore.editorSettings.appLayout;
editShowTrayIcon.value = settingsStore.desktopSettings.show_tray_icon;
@@ -532,6 +537,8 @@ function hasChanges(): boolean {
editShowExecutionTargetPicker.value !== settingsStore.editorSettings.showExecutionTargetPicker ||
editAutoAliasTables.value !== settingsStore.editorSettings.autoAliasTables ||
editWordWrap.value !== settingsStore.editorSettings.wordWrap ||
editSqlSemanticDiagnosticsMode.value !== settingsStore.editorSettings.sqlSemanticDiagnosticsMode ||
editSqlSemanticDiagnosticsEnabled.value !== settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled ||
editConfirmDangerousSqlExecution.value !== settingsStore.editorSettings.confirmDangerousSqlExecution ||
editAppLayout.value !== settingsStore.editorSettings.appLayout ||
editShowTrayIcon.value !== settingsStore.desktopSettings.show_tray_icon ||
@@ -582,6 +589,7 @@ async function persistSettings() {
showExecutionTargetPicker: editShowExecutionTargetPicker.value,
autoAliasTables: editAutoAliasTables.value,
wordWrap: editWordWrap.value,
sqlSemanticDiagnosticsMode: editSqlSemanticDiagnosticsMode.value,
confirmDangerousSqlExecution: editConfirmDangerousSqlExecution.value,
appLayout: editAppLayout.value,
showColumnCommentsInHeader: editShowColumnCommentsInHeader.value,
@@ -643,6 +651,8 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables;
editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap;
editSqlSemanticDiagnosticsMode.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode;
editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled;
editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution;
} else if (tab === "formatter") {
editSqlFormatter.value = normalizeSqlFormatterSettings(DEFAULT_EDITOR_SETTINGS.sqlFormatter);
@@ -702,6 +712,8 @@ function resetAllDefaults() {
editShowExecutionTargetPicker.value = DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker;
editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables;
editWordWrap.value = DEFAULT_EDITOR_SETTINGS.wordWrap;
editSqlSemanticDiagnosticsMode.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode;
editSqlSemanticDiagnosticsEnabled.value = DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsEnabled;
editConfirmDangerousSqlExecution.value = DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution;
editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout;
editShowTrayIcon.value = DEFAULT_DESKTOP_SETTINGS.show_tray_icon;
@@ -866,6 +878,11 @@ function onExecuteModeChange(v: any) {
if (v === "all" || v === "current") editExecuteMode.value = v;
}
function onSqlSemanticDiagnosticsEnabledChange(value: boolean) {
editSqlSemanticDiagnosticsEnabled.value = value;
editSqlSemanticDiagnosticsMode.value = value ? "enabled" : "disabled";
}
function onFontFamilyChange(v: any) {
if (typeof v === "string") editFontFamily.value = v;
}
@@ -2005,14 +2022,26 @@ watch(
</div>
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-confirm-dangerous-sql">{{ t("settings.confirmDangerousSqlExecution") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.confirmDangerousSqlExecutionDescription") }}
</p>
<div class="grid gap-3 md:grid-cols-2">
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-sql-semantic-diagnostics">{{ t("settings.sqlSemanticDiagnosticsEnabled") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.sqlSemanticDiagnosticsEnabledDescription") }}
</p>
</div>
<Switch id="editor-sql-semantic-diagnostics" :model-value="editSqlSemanticDiagnosticsEnabled" class="mt-0.5" @update:model-value="onSqlSemanticDiagnosticsEnabledChange" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="editor-confirm-dangerous-sql">{{ t("settings.confirmDangerousSqlExecution") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.confirmDangerousSqlExecutionDescription") }}
</p>
</div>
<Switch id="editor-confirm-dangerous-sql" v-model="editConfirmDangerousSqlExecution" class="mt-0.5" />
</div>
<Switch id="editor-confirm-dangerous-sql" v-model="editConfirmDangerousSqlExecution" class="mt-0.5" />
</div>
<Separator />
@@ -926,6 +926,17 @@ function setSemanticDiagnostics(next: SqlSemanticDiagnostic[]) {
reconfigureDiagnostics();
}
function clearScheduledSemanticDiagnostics() {
semanticDiagnosticRunId++;
if (semanticDiagnosticTimer) clearTimeout(semanticDiagnosticTimer);
semanticDiagnosticTimer = null;
pendingSemanticDiagnosticPreserveOutsideRanges = false;
}
function shouldSkipSqlSemanticDiagnostics() {
return props.databaseType !== "redis" && !settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled;
}
function rangesOverlap(left: { from: number; to: number }, right: { from: number; to: number }): boolean {
return left.from < right.to && right.from < left.to;
}
@@ -1046,6 +1057,10 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
setSemanticDiagnostics(buildRedisSyntaxDiagnostics(sql));
return;
}
if (shouldSkipSqlSemanticDiagnostics()) {
setSemanticDiagnostics([]);
return;
}
if (!shouldRunSqlSemanticDiagnostics(sql, currentView.state.selection.main.head, { databaseType: props.databaseType })) {
scheduleSemanticDiagnostics(1200, { preserveOutsideRanges: options.preserveOutsideRanges });
return;
@@ -1102,6 +1117,11 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
function scheduleSemanticDiagnostics(delay = 500, options: { preserveOutsideRanges?: boolean } = {}) {
if (!editorIsActive) return;
if (shouldSkipSqlSemanticDiagnostics()) {
clearScheduledSemanticDiagnostics();
setSemanticDiagnostics([]);
return;
}
pendingSemanticDiagnosticPreserveOutsideRanges = !!options.preserveOutsideRanges;
if (semanticDiagnosticTimer) clearTimeout(semanticDiagnosticTimer);
semanticDiagnosticTimer = setTimeout(() => {
@@ -2512,14 +2532,25 @@ watch(
{ deep: true },
);
watch(
() => settingsStore.editorSettings.sqlSemanticDiagnosticsEnabled,
(enabled) => {
if (props.databaseType === "redis") return;
if (!shouldSkipSqlSemanticDiagnostics() && enabled) {
scheduleSemanticDiagnostics(0);
return;
}
clearScheduledSemanticDiagnostics();
setSemanticDiagnostics([]);
},
);
function pauseQueryEditorBackgroundWork() {
flushEditorViewport();
flushEditorSelection();
clearTableNavigationHover();
editorIsActive = false;
semanticDiagnosticRunId++;
if (semanticDiagnosticTimer) clearTimeout(semanticDiagnosticTimer);
semanticDiagnosticTimer = null;
clearScheduledSemanticDiagnostics();
completionEpoch++;
unregisterTableReferenceDropListener();
}
+2
View File
@@ -2671,6 +2671,8 @@ export default {
showExecutionTargetPickerDescription: "When enabled, running without a selection lets you choose between the current statement and all SQL.",
wordWrap: "Word wrap",
wordWrapDescription: "Wrap long SQL lines within the editor width",
sqlSemanticDiagnosticsEnabled: "SQL semantic diagnostics",
sqlSemanticDiagnosticsEnabledDescription: "When enabled, the editor reports semantic issues such as unknown tables and columns. Disable it to reduce parsing and metadata checks for large SQL.",
confirmDangerousSqlExecution: "Confirm before dangerous SQL",
confirmDangerousSqlExecutionDescription: "When disabled, ALTER, DROP, DELETE, TRUNCATE, and other dangerous SQL run without the warning dialog.",
autoAliasTables: "Automatically add table aliases",
+2
View File
@@ -2644,6 +2644,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "有効にすると、選択なしで実行するときに現在の文とすべてのSQLを一時的に選べます。",
wordWrap: "折り返し",
wordWrapDescription: "長いSQL行をエディタ幅内で折り返します",
sqlSemanticDiagnosticsEnabled: "SQLセマンティック診断",
sqlSemanticDiagnosticsEnabledDescription: "有効時、エディタは不明なテーブルや列などの意味的な問題を表示します。大きなSQLの解析とメタデータ確認の負荷を減らすには無効にします。",
confirmDangerousSqlExecution: "危険なSQLの前に確認",
confirmDangerousSqlExecutionDescription: "無効時、ALTER、DROP、DELETE、TRUNCATEなどの危険なSQLが警告ダイアログなしで実行されます。",
autoAliasTables: "テーブル別名を自動追加",
+2
View File
@@ -2678,6 +2678,8 @@ export default withEnglishFallback({
showExecutionTargetPickerDescription: "开启后,无选区执行时可在当前语句和全部 SQL 之间临时选择。",
wordWrap: "自动换行",
wordWrapDescription: "长 SQL 在编辑器宽度内自动折行显示",
sqlSemanticDiagnosticsEnabled: "SQL 语义诊断",
sqlSemanticDiagnosticsEnabledDescription: "开启后,编辑器会提示未知表、字段等语义问题;关闭可减少 SQL 解析和元数据检查的性能开销。",
confirmDangerousSqlExecution: "执行危险 SQL 前弹出确认",
confirmDangerousSqlExecutionDescription: "关闭后,ALTER、DROP、DELETE、TRUNCATE 等危险 SQL 将直接执行。",
autoAliasTables: "自动添加表别名",
+105 -6
View File
@@ -1,7 +1,7 @@
import type { SqlCompletionColumn, SqlCompletionTable } from "@/lib/sqlCompletion";
import { getSqlCompletionContext } from "@/lib/sqlCompletion";
import { executableStatementRanges, type SqlTextRange } from "@/lib/sqlStatementRanges";
import type { DatabaseType, SqlColumnReference, SqlReferenceAnalysis, SqlTableReference, SqlTextSpan } from "@/types/database";
import type { DatabaseType, SqlColumnReference, SqlReferenceAnalysis, SqlReferenceScope, SqlTableReference, SqlTextSpan } from "@/types/database";
export interface SqlSemanticDiagnostic {
span: SqlTextSpan;
@@ -42,6 +42,7 @@ export function buildSqlSemanticDiagnostics(analysis: SqlReferenceAnalysis, sche
const diagnostics: SqlSemanticDiagnostic[] = [];
const tables = analysis.tables.filter((table) => table.name.trim());
const knownTables = new Map<string, SqlTableReference>();
const scopesById = scopesByIdMap(analysis.scopes);
for (const table of tables) {
knownTables.set(normalizeName(table.name), table);
@@ -52,14 +53,14 @@ export function buildSqlSemanticDiagnostics(analysis: SqlReferenceAnalysis, sche
for (const table of tables) {
if (!schema.missingTables?.has(tableReferenceKey(table))) continue;
diagnostics.push({
span: table.span,
span: trimSqlTextSpanWhitespace(schema.sql, table.span),
message: `Unknown table ${displayTableName(table)}`,
severity: "error",
});
}
for (const column of analysis.columns) {
const table = resolveColumnTable(column, tables, knownTables, schema.sql);
const table = resolveColumnTable(column, tables, knownTables, schema.sql, scopesById);
if (!table) continue;
if (schema.missingTables?.has(tableReferenceKey(table))) continue;
@@ -71,7 +72,7 @@ export function buildSqlSemanticDiagnostics(analysis: SqlReferenceAnalysis, sche
const displayName = column.qualifier ? `${column.qualifier}.${column.name}` : column.name;
diagnostics.push({
span: column.span,
span: trimSqlTextSpanWhitespace(schema.sql, column.span),
message: `Unknown column ${displayName}`,
severity: "error",
});
@@ -80,6 +81,70 @@ export function buildSqlSemanticDiagnostics(analysis: SqlReferenceAnalysis, sche
return diagnostics;
}
function trimSqlTextSpanWhitespace(sql: string | undefined, span: SqlTextSpan): SqlTextSpan {
if (!sql) return span;
const range = sqlTextSpanToOffsetRange(sql, span);
if (!range) return span;
let from = range.from;
let to = range.to;
while (from < to && /\s/.test(sql[from] ?? "")) from += 1;
while (to > from && /\s/.test(sql[to - 1] ?? "")) to -= 1;
if (from === range.from && to === range.to) return span;
const start = offsetToSqlTextStartPosition(sql, from);
const end = offsetToSqlTextEndPosition(sql, to);
if (!start || !end) return span;
return {
start_line: start.line,
start_column: start.column,
end_line: end.line,
end_column: Math.max(end.column, start.column),
};
}
function sqlTextSpanToOffsetRange(sql: string, span: SqlTextSpan): { from: number; to: number } | null {
if (!span.start_line || !span.start_column) return null;
const from = sqlTextPositionToOffset(sql, span.start_line, span.start_column - 1);
const to = sqlTextPositionToOffset(sql, Math.max(span.end_line, span.start_line), Math.max(span.end_column, span.start_column));
if (from == null || to == null || to <= from) return null;
return { from, to };
}
function sqlTextPositionToOffset(sql: string, line: number, column: number): number | null {
const lines = sql.split(/\r?\n/);
if (line < 1 || line > lines.length) return null;
let offset = 0;
for (let index = 0; index < line - 1; index += 1) {
offset += lines[index].length + 1;
}
return Math.min(offset + Math.max(column, 0), offset + lines[line - 1].length);
}
function offsetToSqlTextStartPosition(sql: string, offset: number): { line: number; column: number } | null {
const position = offsetToLineColumn(sql, offset);
return position ? { line: position.line, column: position.column + 1 } : null;
}
function offsetToSqlTextEndPosition(sql: string, offset: number): { line: number; column: number } | null {
return offsetToLineColumn(sql, offset);
}
function offsetToLineColumn(sql: string, offset: number): { line: number; column: number } | null {
if (offset < 0 || offset > sql.length) return null;
const lines = sql.split(/\r?\n/);
let remaining = offset;
for (let index = 0; index < lines.length; index += 1) {
const lineLength = lines[index].length;
if (remaining <= lineLength) {
return { line: index + 1, column: remaining };
}
remaining -= lineLength + 1;
}
const lastLine = lines[lines.length - 1] ?? "";
return { line: lines.length, column: lastLine.length };
}
export function buildSqlParserErrorDiagnostic(error: unknown, sql: string): SqlSemanticDiagnostic | null {
const message = errorMessage(error);
const location = /\bat Line:\s*(\d+),\s*Column:\s*(\d+)\b/i.exec(message);
@@ -129,8 +194,8 @@ export function isSqlSemanticDiagnosticInputContext(sql: string, cursor: number,
return context.exclusiveColumnSuggestions || !!context.qualifier || ((context.suggestTables || context.exclusiveTableSuggestions) && isCursorAfterTableTrigger(sql, cursor));
}
function resolveColumnTable(column: SqlColumnReference, tables: SqlTableReference[], knownTables: Map<string, SqlTableReference>, sql?: string): SqlTableReference | null {
const candidateTables = sql ? tablesInSameStatement(tables, column, sql) : tables;
function resolveColumnTable(column: SqlColumnReference, tables: SqlTableReference[], knownTables: Map<string, SqlTableReference>, sql?: string, scopesById?: Map<number, SqlReferenceScope>): SqlTableReference | null {
const candidateTables = candidateTablesForColumn(tables, column, sql, scopesById);
if (column.qualifier) {
return tableLookupFor(candidateTables).get(normalizeName(column.qualifier)) ?? (sql ? null : (knownTables.get(normalizeName(column.qualifier)) ?? null));
}
@@ -138,6 +203,34 @@ function resolveColumnTable(column: SqlColumnReference, tables: SqlTableReferenc
return candidateTables[0];
}
function candidateTablesForColumn(tables: SqlTableReference[], column: SqlColumnReference, sql?: string, scopesById?: Map<number, SqlReferenceScope>): SqlTableReference[] {
const scoped = tablesInVisibleScopes(tables, column, scopesById);
if (scoped) return scoped;
return sql ? tablesInSameStatement(tables, column, sql) : tables;
}
function tablesInVisibleScopes(tables: SqlTableReference[], column: SqlColumnReference, scopesById?: Map<number, SqlReferenceScope>): SqlTableReference[] | null {
if (column.scope_id == null || !scopesById || scopesById.size === 0) return null;
if (!column.qualifier) {
const currentScopeTables = tables.filter((table) => table.scope_id === column.scope_id);
if (currentScopeTables.length > 0) return currentScopeTables;
}
const visibleScopeIds = scopeAndParents(column.scope_id, scopesById);
if (visibleScopeIds.size === 0) return null;
return tables.filter((table) => table.scope_id != null && visibleScopeIds.has(table.scope_id));
}
function scopeAndParents(scopeId: number, scopesById: Map<number, SqlReferenceScope>): Set<number> {
const ids = new Set<number>();
let current: number | undefined = scopeId;
while (current != null && !ids.has(current)) {
ids.add(current);
const scope = scopesById.get(current);
current = scope?.parent_id ?? undefined;
}
return ids;
}
function tableLookupFor(tables: SqlTableReference[]): Map<string, SqlTableReference> {
const lookup = new Map<string, SqlTableReference>();
for (const table of tables) {
@@ -157,6 +250,12 @@ function tablesInSameStatement(tables: SqlTableReference[], column: SqlColumnRef
});
}
function scopesByIdMap(scopes: readonly SqlReferenceScope[] | undefined): Map<number, SqlReferenceScope> {
const map = new Map<number, SqlReferenceScope>();
for (const scope of scopes ?? []) map.set(scope.id, scope);
return map;
}
function spanStartOffset(sql: string, span: SqlTextSpan): number | null {
if (!span.start_line || !span.start_column) return null;
const lines = sql.split(/\r?\n/);
@@ -10,6 +10,22 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ autoAliasTables: false }).autoAliasTables).toBe(false);
});
it("keeps SQL semantic diagnostics in auto mode and disabled by default", () => {
const settings = normalizeEditorSettings({});
expect(settings.sqlSemanticDiagnosticsMode).toBe("auto");
expect(settings.sqlSemanticDiagnosticsEnabled).toBe(false);
});
it("preserves explicit SQL semantic diagnostics modes", () => {
expect(normalizeEditorSettings({ sqlSemanticDiagnosticsMode: "enabled" }).sqlSemanticDiagnosticsEnabled).toBe(true);
expect(normalizeEditorSettings({ sqlSemanticDiagnosticsMode: "disabled" }).sqlSemanticDiagnosticsEnabled).toBe(false);
});
it("migrates legacy SQL semantic diagnostics booleans to explicit modes", () => {
expect(normalizeEditorSettings({ sqlSemanticDiagnosticsEnabled: true } as any).sqlSemanticDiagnosticsMode).toBe("enabled");
expect(normalizeEditorSettings({ sqlSemanticDiagnosticsEnabled: false } as any).sqlSemanticDiagnosticsMode).toBe("disabled");
});
it("defaults update downloads to the official source", () => {
expect(normalizeEditorSettings({}).updateDownloadSource).toBe("official");
});
+26
View File
@@ -59,8 +59,10 @@ export type DesktopIconTheme = "default" | "black";
export type InterfaceLayout = "separated" | "classic";
export type UpdateDownloadSource = "official" | "cnb";
export type SqlSemanticDiagnosticsMode = "auto" | "enabled" | "disabled";
export const DEFAULT_SIDEBAR_TABLE_PAGE_SIZE = 1000;
const SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED = false;
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
show_tray_icon: true,
@@ -330,6 +332,8 @@ export interface EditorSettings {
showExecutionTargetPicker: boolean;
autoAliasTables: boolean;
wordWrap: boolean;
sqlSemanticDiagnosticsMode: SqlSemanticDiagnosticsMode;
sqlSemanticDiagnosticsEnabled: boolean;
confirmDangerousSqlExecution: boolean;
compactTabTitle: boolean;
appLayout: "separated" | "classic";
@@ -436,6 +440,8 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
showExecutionTargetPicker: false,
autoAliasTables: true,
wordWrap: false,
sqlSemanticDiagnosticsMode: "auto",
sqlSemanticDiagnosticsEnabled: SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED,
confirmDangerousSqlExecution: true,
compactTabTitle: false,
appLayout: "classic",
@@ -514,6 +520,18 @@ function normalizeUpdateDownloadSource(value: unknown): UpdateDownloadSource {
return value === "cnb" ? "cnb" : DEFAULT_EDITOR_SETTINGS.updateDownloadSource;
}
function normalizeSqlSemanticDiagnosticsMode(value: unknown, legacyEnabled?: unknown): SqlSemanticDiagnosticsMode {
if (value === "auto" || value === "enabled" || value === "disabled") return value;
if (typeof legacyEnabled === "boolean") return legacyEnabled ? "enabled" : "disabled";
return DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode;
}
function sqlSemanticDiagnosticsEnabledForMode(mode: SqlSemanticDiagnosticsMode): boolean {
if (mode === "enabled") return true;
if (mode === "disabled") return false;
return SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED;
}
function normalizeDisconnectTabHandlingMode(value: unknown, legacyCloseTabsOnDisconnect?: unknown): DisconnectTabHandlingMode {
if (DISCONNECT_TAB_HANDLING_MODES.includes(value as DisconnectTabHandlingMode)) {
return value as DisconnectTabHandlingMode;
@@ -582,6 +600,7 @@ function normalizeToolbarItems(items: Partial<ToolbarItems> | undefined): Toolba
}
export function normalizeEditorSettings(settings: Partial<EditorSettings>, existing?: EditorSettings): EditorSettings {
const sqlSemanticDiagnosticsMode = normalizeSqlSemanticDiagnosticsMode(settings.sqlSemanticDiagnosticsMode, settings.sqlSemanticDiagnosticsEnabled);
return {
fontFamily: settings.fontFamily ?? DEFAULT_EDITOR_SETTINGS.fontFamily,
fontSize: settings.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize,
@@ -618,6 +637,8 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
showExecutionTargetPicker: settings.showExecutionTargetPicker ?? DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker,
autoAliasTables: settings.autoAliasTables ?? DEFAULT_EDITOR_SETTINGS.autoAliasTables,
wordWrap: settings.wordWrap ?? DEFAULT_EDITOR_SETTINGS.wordWrap,
sqlSemanticDiagnosticsMode,
sqlSemanticDiagnosticsEnabled: sqlSemanticDiagnosticsEnabledForMode(sqlSemanticDiagnosticsMode),
confirmDangerousSqlExecution: settings.confirmDangerousSqlExecution ?? DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution,
compactTabTitle: settings.compactTabTitle ?? DEFAULT_EDITOR_SETTINGS.compactTabTitle,
appLayout: settings.appLayout ?? DEFAULT_EDITOR_SETTINGS.appLayout,
@@ -791,6 +812,11 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.showExecutionTargetPicker !== undefined) editorSettings.value.showExecutionTargetPicker = partial.showExecutionTargetPicker;
if (partial.autoAliasTables !== undefined) editorSettings.value.autoAliasTables = partial.autoAliasTables;
if (partial.wordWrap !== undefined) editorSettings.value.wordWrap = partial.wordWrap;
if (partial.sqlSemanticDiagnosticsMode !== undefined || partial.sqlSemanticDiagnosticsEnabled !== undefined) {
const nextMode = normalizeSqlSemanticDiagnosticsMode(partial.sqlSemanticDiagnosticsMode, partial.sqlSemanticDiagnosticsEnabled);
editorSettings.value.sqlSemanticDiagnosticsMode = nextMode;
editorSettings.value.sqlSemanticDiagnosticsEnabled = sqlSemanticDiagnosticsEnabledForMode(nextMode);
}
if (partial.confirmDangerousSqlExecution !== undefined) editorSettings.value.confirmDangerousSqlExecution = partial.confirmDangerousSqlExecution;
if (partial.compactTabTitle !== undefined) editorSettings.value.compactTabTitle = partial.compactTabTitle;
if (partial.appLayout !== undefined) editorSettings.value.appLayout = partial.appLayout;
+8
View File
@@ -448,17 +448,25 @@ export interface SqlTableReference {
schema?: string | null;
alias?: string | null;
span: SqlTextSpan;
scope_id?: number;
}
export interface SqlColumnReference {
name: string;
qualifier?: string | null;
span: SqlTextSpan;
scope_id?: number;
}
export interface SqlReferenceScope {
id: number;
parent_id?: number | null;
}
export interface SqlReferenceAnalysis {
tables: SqlTableReference[];
columns: SqlColumnReference[];
scopes?: SqlReferenceScope[];
}
export type TreeNodeType =