feat(sql): add schema qualification completion setting

Closes #9219
This commit is contained in:
t8y2
2026-09-27 12:04:10 +08:00
committed by t8y2
parent 4a7b8f0f27
commit 627a8498d0
27 changed files with 339 additions and 15 deletions
@@ -108,6 +108,7 @@ import {
type ClickTableNavigationTarget,
type EditorSettings,
type SqlCompletionTriggerMode,
type SqlTableCompletionSchemaQualification,
type TableHoverLookupMode,
SIDEBAR_INDENT_MIN,
SIDEBAR_INDENT_MAX,
@@ -623,6 +624,15 @@ const editShowLineNumbers = ref(settingsStore.editorSettings.showLineNumbers);
const editShowCurrentStatementFrame = ref(settingsStore.editorSettings.showCurrentStatementFrame);
const editShowInsertValueHints = ref(settingsStore.editorSettings.showInsertValueHints);
const editAutoAliasTables = ref(settingsStore.editorSettings.autoAliasTables);
const editTableCompletionSchemaQualification = ref<SqlTableCompletionSchemaQualification>(settingsStore.editorSettings.tableCompletionSchemaQualification);
const tableCompletionSchemaQualificationDescription = computed(() => {
const key = {
never: "settings.tableCompletionSchemaQualificationNeverDescription",
collision: "settings.tableCompletionSchemaQualificationCollisionDescription",
always: "settings.tableCompletionSchemaQualificationAlwaysDescription",
}[editTableCompletionSchemaQualification.value];
return t(key);
});
const editInsertSpaceAfterCompletion = ref(settingsStore.editorSettings.insertSpaceAfterCompletion);
const editSqlServerSpaceConfirmsCompletion = ref(settingsStore.editorSettings.sqlServerSpaceConfirmsCompletion);
const showSqlServerSpaceConfirmsCompletion = computed(() => hasSqlServerConnection.value || settingsStore.editorSettings.sqlServerSpaceConfirmsCompletion || editSqlServerSpaceConfirmsCompletion.value);
@@ -991,6 +1001,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
showCurrentStatementFrame: editShowCurrentStatementFrame.value,
showInsertValueHints: editShowInsertValueHints.value,
autoAliasTables: editAutoAliasTables.value,
tableCompletionSchemaQualification: editTableCompletionSchemaQualification.value,
insertSpaceAfterCompletion: editInsertSpaceAfterCompletion.value,
sqlServerSpaceConfirmsCompletion: editSqlServerSpaceConfirmsCompletion.value,
sortCompletionColumnsAlphabetically: editSortCompletionColumnsAlphabetically.value,
@@ -1637,6 +1648,7 @@ function syncEditorSettingsDraftFromStore() {
editShowCurrentStatementFrame.value = settingsStore.editorSettings.showCurrentStatementFrame;
editShowInsertValueHints.value = settingsStore.editorSettings.showInsertValueHints;
editAutoAliasTables.value = settingsStore.editorSettings.autoAliasTables;
editTableCompletionSchemaQualification.value = settingsStore.editorSettings.tableCompletionSchemaQualification;
editInsertSpaceAfterCompletion.value = settingsStore.editorSettings.insertSpaceAfterCompletion;
editSqlServerSpaceConfirmsCompletion.value = settingsStore.editorSettings.sqlServerSpaceConfirmsCompletion;
editSortCompletionColumnsAlphabetically.value = settingsStore.editorSettings.sortCompletionColumnsAlphabetically;
@@ -1775,6 +1787,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
showCurrentStatementFrame: editShowCurrentStatementFrame,
showInsertValueHints: editShowInsertValueHints,
autoAliasTables: editAutoAliasTables,
tableCompletionSchemaQualification: editTableCompletionSchemaQualification,
insertSpaceAfterCompletion: editInsertSpaceAfterCompletion,
sqlServerSpaceConfirmsCompletion: editSqlServerSpaceConfirmsCompletion,
sortCompletionColumnsAlphabetically: editSortCompletionColumnsAlphabetically,
@@ -2263,6 +2276,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editShowCurrentStatementFrame.value = DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame;
editShowInsertValueHints.value = DEFAULT_EDITOR_SETTINGS.showInsertValueHints;
editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables;
editTableCompletionSchemaQualification.value = DEFAULT_EDITOR_SETTINGS.tableCompletionSchemaQualification;
editInsertSpaceAfterCompletion.value = DEFAULT_EDITOR_SETTINGS.insertSpaceAfterCompletion;
editSqlServerSpaceConfirmsCompletion.value = DEFAULT_EDITOR_SETTINGS.sqlServerSpaceConfirmsCompletion;
editSortCompletionColumnsAlphabetically.value = DEFAULT_EDITOR_SETTINGS.sortCompletionColumnsAlphabetically;
@@ -2421,6 +2435,7 @@ function resetAllDefaults() {
editShowCurrentStatementFrame.value = DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame;
editShowInsertValueHints.value = DEFAULT_EDITOR_SETTINGS.showInsertValueHints;
editAutoAliasTables.value = DEFAULT_EDITOR_SETTINGS.autoAliasTables;
editTableCompletionSchemaQualification.value = DEFAULT_EDITOR_SETTINGS.tableCompletionSchemaQualification;
editInsertSpaceAfterCompletion.value = DEFAULT_EDITOR_SETTINGS.insertSpaceAfterCompletion;
editSqlServerSpaceConfirmsCompletion.value = DEFAULT_EDITOR_SETTINGS.sqlServerSpaceConfirmsCompletion;
editSortCompletionColumnsAlphabetically.value = DEFAULT_EDITOR_SETTINGS.sortCompletionColumnsAlphabetically;
@@ -2669,6 +2684,12 @@ function onCompletionTriggerModeChange(v: any) {
}
}
function onTableCompletionSchemaQualificationChange(v: any) {
if (v === "never" || v === "collision" || v === "always") {
editTableCompletionSchemaQualification.value = v;
}
}
function onTableHoverLookupModeChange(v: any) {
if (v === "current" || v === "fallback" || v === "always") {
editTableHoverLookupMode.value = v;
@@ -6449,6 +6470,25 @@ onUnmounted(() => {
</div>
<Switch id="editor-auto-alias-tables" v-model="editAutoAliasTables" class="mt-0.5" />
</div>
<div class="settings-item flex items-start justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2" data-editor-table-completion-schema-qualification>
<div class="min-w-0 space-y-1">
<Label>{{ t("settings.tableCompletionSchemaQualification") }}</Label>
<p class="text-xs leading-tight text-muted-foreground">
{{ tableCompletionSchemaQualificationDescription }}
</p>
</div>
<Select :model-value="editTableCompletionSchemaQualification" @update:model-value="onTableCompletionSchemaQualificationChange">
<SelectTrigger class="h-8 w-44 shrink-0">
<SelectValue :placeholder="t('settings.tableCompletionSchemaQualification')" />
</SelectTrigger>
<SelectContent>
<SelectItem value="never">{{ t("settings.tableCompletionSchemaQualificationNever") }}</SelectItem>
<SelectItem value="collision">{{ t("settings.tableCompletionSchemaQualificationCollision") }}</SelectItem>
<SelectItem value="always">{{ t("settings.tableCompletionSchemaQualificationAlways") }}</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Separator />
@@ -63,7 +63,7 @@ function createHarness(overrides: Partial<QueryEditorProps> = {}) {
listElasticsearchCompletionFields: vi.fn(async () => []),
};
const connectionStore = store as unknown as Options["connectionStore"];
const settings = reactive({ editorSettings: { completionTriggerMode: "positional", snippets: [], sqlFormatter: { keywordCase: "upper", functionCase: "upper" }, autoAliasTables: false, generateSqlQuoteIdentifiers: false } });
const settings = reactive({ editorSettings: { completionTriggerMode: "positional", snippets: [], sqlFormatter: { keywordCase: "upper", functionCase: "upper" }, autoAliasTables: false, tableCompletionSchemaQualification: "collision", generateSqlQuoteIdentifiers: false } });
const metadata = useQueryEditorCompletionMetadata({ props, view, connectionStore, sqlBehaviorDialect: () => props.dialect, remoteLatencyBudgetMs: 40, maxCompletionTables: 100, onDemandMinPrefix: 2, semanticCompletionEnabled: false });
const startCompletion = vi.fn(() => true);
const runtime: Options["runtime"] = { codeMirrorStartCompletion: startCompletion, codeMirrorInsertCompletionText: insertCompletionText, codeMirrorSnippetCompletion: snippetCompletion, codeMirrorCompletionStatus: () => null, imeCompositionActive: false };
@@ -811,6 +811,7 @@ export function useQueryEditorCompletion(options: QueryEditorCompletionOptions)
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
functionCase: settingsStore.editorSettings.sqlFormatter.functionCase,
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
tableCompletionSchemaQualification: settingsStore.editorSettings.tableCompletionSchemaQualification,
quoteIdentifiers: settingsStore.editorSettings.generateSqlQuoteIdentifiers,
});
return buildSqlCompletionResult(items, completionContext, fullDoc, position);
@@ -873,6 +874,7 @@ export function useQueryEditorCompletion(options: QueryEditorCompletionOptions)
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
functionCase: settingsStore.editorSettings.sqlFormatter.functionCase,
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
tableCompletionSchemaQualification: settingsStore.editorSettings.tableCompletionSchemaQualification,
quoteIdentifiers: settingsStore.editorSettings.generateSqlQuoteIdentifiers,
});
return buildSqlCompletionResult(items, completionContext, fullDoc, position);
@@ -1194,6 +1196,7 @@ export function useQueryEditorCompletion(options: QueryEditorCompletionOptions)
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
functionCase: settingsStore.editorSettings.sqlFormatter.functionCase,
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
tableCompletionSchemaQualification: settingsStore.editorSettings.tableCompletionSchemaQualification,
quoteIdentifiers: settingsStore.editorSettings.generateSqlQuoteIdentifiers,
});
@@ -1655,6 +1658,7 @@ export function useQueryEditorCompletion(options: QueryEditorCompletionOptions)
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
functionCase: settingsStore.editorSettings.sqlFormatter.functionCase,
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
tableCompletionSchemaQualification: settingsStore.editorSettings.tableCompletionSchemaQualification,
quoteIdentifiers: settingsStore.editorSettings.generateSqlQuoteIdentifiers,
});
@@ -72,6 +72,7 @@ const FEATURE_MESSAGES: FeatureMessages[] = [
{ feature: "sidebar search skip hint", keys: ["sidebar.searchConnectionSkipped"] },
{ feature: "PostgreSQL legacy TLS", keys: ["connection.postgresLegacyTls", "connection.postgresLegacyTlsHint"] },
{ feature: "settings search sections", keys: ["settings.syncWebDavWebDescription", "settings.performanceSection"] },
{ feature: "SQL table completion schema qualification (#9219)", keys: ["settings.tableCompletionSchemaQualification*"], translated: true },
{ feature: "Redis batch expiration", keys: under("redis", ["batchExpiry", "batchExpiryTitle", "batchExpirySelected", "batchExpiryApply", "batchExpirySuccess", "batchExpiryPartial"]) },
{
feature: "plugin AI tools and data access",
+8
View File
@@ -7740,6 +7740,14 @@ export default withEnglishFallback({
sqlVariableSyntax_sqlserverDescription: "SQL Server üslublu yer tutucuları əvəz etmək üçün dəyər soruş (sessiya dəyişənləri ilə toqquşa bilər).",
sqlVariableSyntax_atSet: "Sətirdaxili dəyişənlərin açılması",
sqlVariableSyntax_atSetDescription: "İşlətməzdən əvvəl sətirdaxili dəyişən elanlarını aç və dəyərlərini yerinə qoy.",
tableCompletionSchemaQualification: "Cədvəl tamamlamasında sxem ixtisaslaşdırması",
tableCompletionSchemaQualificationDescription: "Tamamlanan cədvəl və görünüş adlarına sxemin nə vaxt əlavə ediləcəyini seçin.",
tableCompletionSchemaQualificationNever: "Heç vaxt",
tableCompletionSchemaQualificationNeverDescription: "Yalnız cədvəl və ya görünüş adını daxil edir.",
tableCompletionSchemaQualificationCollision: "Ad toqquşduqda",
tableCompletionSchemaQualificationCollisionDescription: "Cari avtomatik davranışdan istifadə edir və sxemi yalnız lazım olduqda əlavə edir.",
tableCompletionSchemaQualificationAlways: "Həmişə",
tableCompletionSchemaQualificationAlwaysDescription: "Sxem metadata-da olduqda həmişə əlavə edir.",
autoAliasTables: "Cədvəl ləqəblərini avtomatik əlavə et",
autoAliasTablesDescription: "FROM və ya JOIN daxilində cədvəl tamamlaması seçilərkən order_items AS oi kimi yaradılmış ləqəb daxil et.",
sqlServerSpaceConfirmsCompletion: "Boşluqla tamamlamanı təsdiqlə (SQL Server üslubu)",
+8
View File
@@ -8414,6 +8414,14 @@ export default {
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.",
tableCompletionSchemaQualification: "Schema qualification for table completion",
tableCompletionSchemaQualificationDescription: "Choose when completed table and view names include their schema.",
tableCompletionSchemaQualificationNever: "Never",
tableCompletionSchemaQualificationNeverDescription: "Insert only the table or view name.",
tableCompletionSchemaQualificationCollision: "On collision",
tableCompletionSchemaQualificationCollisionDescription: "Use the current automatic behavior and include a schema only when needed.",
tableCompletionSchemaQualificationAlways: "Always",
tableCompletionSchemaQualificationAlwaysDescription: "Include the schema whenever it is available in metadata.",
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.",
sqlServerSpaceConfirmsCompletion: "Space confirms completion (SQL Server style)",
+8
View File
@@ -7749,6 +7749,14 @@ export default withEnglishFallback({
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.",
tableCompletionSchemaQualification: "Calificación de esquema al completar tablas",
tableCompletionSchemaQualificationDescription: "Elige cuándo los nombres de tablas y vistas completados incluyen su esquema.",
tableCompletionSchemaQualificationNever: "Nunca",
tableCompletionSchemaQualificationNeverDescription: "Inserta solo el nombre de la tabla o vista.",
tableCompletionSchemaQualificationCollision: "Si hay conflicto",
tableCompletionSchemaQualificationCollisionDescription: "Usa el comportamiento automático actual e incluye el esquema solo cuando sea necesario.",
tableCompletionSchemaQualificationAlways: "Siempre",
tableCompletionSchemaQualificationAlwaysDescription: "Incluye el esquema siempre que esté disponible en los metadatos.",
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.",
sqlServerSpaceConfirmsCompletion: "Confirmar la sugerencia con espacio (estilo SQL Server)",
+8
View File
@@ -7674,6 +7674,14 @@ export default withEnglishFallback({
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.",
tableCompletionSchemaQualification: "Qualificazione dello schema nel completamento tabelle",
tableCompletionSchemaQualificationDescription: "Scegli quando i nomi completati di tabelle e viste includono lo schema.",
tableCompletionSchemaQualificationNever: "Mai",
tableCompletionSchemaQualificationNeverDescription: "Inserisce solo il nome della tabella o della vista.",
tableCompletionSchemaQualificationCollision: "In caso di conflitto",
tableCompletionSchemaQualificationCollisionDescription: "Usa il comportamento automatico attuale e include lo schema solo quando necessario.",
tableCompletionSchemaQualificationAlways: "Sempre",
tableCompletionSchemaQualificationAlwaysDescription: "Include lo schema ogni volta che è disponibile nei metadati.",
autoAliasTables: "Aggiungi alias tabella automaticamente",
autoAliasTablesDescription: "Quando scegli una tabella in FROM o JOIN, inserisce un alias generato come order_items AS oi.",
sqlServerSpaceConfirmsCompletion: "Conferma il completamento con lo spazio (stile SQL Server)",
+8
View File
@@ -7679,6 +7679,14 @@ export default withEnglishFallback({
sqlVariableSyntax_sqlserverDescription: "SQL Server 形式プレースホルダーを置き換える値の入力を求めます(セッション変数と競合する場合があります)。",
sqlVariableSyntax_atSet: "インライン変数展開",
sqlVariableSyntax_atSetDescription: "実行前にインライン変数宣言を展開し、その値をインライン化します。",
tableCompletionSchemaQualification: "テーブル補完のスキーマ修飾",
tableCompletionSchemaQualificationDescription: "補完したテーブル名とビュー名にスキーマを含めるタイミングを選択します。",
tableCompletionSchemaQualificationNever: "しない",
tableCompletionSchemaQualificationNeverDescription: "テーブル名またはビュー名だけを挿入します。",
tableCompletionSchemaQualificationCollision: "名前が重複する場合",
tableCompletionSchemaQualificationCollisionDescription: "現在の自動動作を使用し、必要な場合だけスキーマを挿入します。",
tableCompletionSchemaQualificationAlways: "常に",
tableCompletionSchemaQualificationAlwaysDescription: "メタデータにスキーマがある場合は常に挿入します。",
autoAliasTables: "テーブル別名を自動追加",
autoAliasTablesDescription: "FROM または JOIN でテーブル補完を選択すると、order_items AS oi のような生成済み別名を挿入します。",
sqlServerSpaceConfirmsCompletion: "スペースで補完を確定(SQL Server 方式)",
+8
View File
@@ -7454,6 +7454,14 @@ export default withEnglishFallback({
sqlVariableSyntax_sqlserverDescription: "SQL Server 스타일 자리표시자를 치환할 값을 입력하라는 메시지를 표시합니다 (세션 변수와 충돌할 수 있음).",
sqlVariableSyntax_atSet: "인라인 변수 확장",
sqlVariableSyntax_atSetDescription: "실행 전에 인라인 변수 선언을 확장하고 값을 치환합니다.",
tableCompletionSchemaQualification: "테이블 완성 스키마 한정",
tableCompletionSchemaQualificationDescription: "완성된 테이블 및 뷰 이름에 스키마를 포함할 시점을 선택합니다.",
tableCompletionSchemaQualificationNever: "안 함",
tableCompletionSchemaQualificationNeverDescription: "테이블 또는 뷰 이름만 삽입합니다.",
tableCompletionSchemaQualificationCollision: "이름 충돌 시",
tableCompletionSchemaQualificationCollisionDescription: "현재 자동 동작을 사용하여 필요한 경우에만 스키마를 삽입합니다.",
tableCompletionSchemaQualificationAlways: "항상",
tableCompletionSchemaQualificationAlwaysDescription: "메타데이터에 스키마가 있으면 항상 삽입합니다.",
autoAliasTables: "자동으로 테이블 별칭 추가",
autoAliasTablesDescription: "FROM이나 JOIN에서 테이블 완성을 선택할 때 order_items AS oi 같은 생성된 별칭을 삽입합니다.",
sqlServerSpaceConfirmsCompletion: "공백으로 완성 확정(SQL Server 방식)",
+8
View File
@@ -7675,6 +7675,14 @@ export default withEnglishFallback({
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.",
tableCompletionSchemaQualification: "Qualificação de schema no autocompletar de tabelas",
tableCompletionSchemaQualificationDescription: "Escolha quando nomes de tabelas e views autocompletados incluem o schema.",
tableCompletionSchemaQualificationNever: "Nunca",
tableCompletionSchemaQualificationNeverDescription: "Insere apenas o nome da tabela ou view.",
tableCompletionSchemaQualificationCollision: "Em caso de conflito",
tableCompletionSchemaQualificationCollisionDescription: "Usa o comportamento automático atual e inclui o schema apenas quando necessário.",
tableCompletionSchemaQualificationAlways: "Sempre",
tableCompletionSchemaQualificationAlwaysDescription: "Inclui o schema sempre que ele estiver disponível nos metadados.",
autoAliasTables: "Adicionar alias de tabela automaticamente",
autoAliasTablesDescription: "Ao escolher uma tabela em FROM ou JOIN, insere um alias gerado como order_items AS oi.",
sqlServerSpaceConfirmsCompletion: "Confirmar a conclusão com espaço (estilo SQL Server)",
+8
View File
@@ -8934,6 +8934,14 @@ export default withEnglishFallback({
sqlVariableSyntax_sqlserverDescription: "Запрашивать значение для подстановки заполнителей в стиле SQL Server (может конфликтовать с переменными сеанса).",
sqlVariableSyntax_atSet: "Раскрытие встроенных переменных",
sqlVariableSyntax_atSetDescription: "Раскрывать встроенные объявления переменных и подставлять их значения перед выполнением.",
tableCompletionSchemaQualification: "Указание схемы при автодополнении таблиц",
tableCompletionSchemaQualificationDescription: "Выберите, когда дополненные имена таблиц и представлений должны включать схему.",
tableCompletionSchemaQualificationNever: "Никогда",
tableCompletionSchemaQualificationNeverDescription: "Вставлять только имя таблицы или представления.",
tableCompletionSchemaQualificationCollision: "При совпадении имён",
tableCompletionSchemaQualificationCollisionDescription: "Использовать текущее автоматическое поведение и добавлять схему только при необходимости.",
tableCompletionSchemaQualificationAlways: "Всегда",
tableCompletionSchemaQualificationAlwaysDescription: "Добавлять схему всегда, когда она доступна в метаданных.",
autoAliasTables: "Автоматически добавлять псевдонимы таблиц",
autoAliasTablesDescription: "При выборе автодополнения таблицы в FROM или JOIN вставлять сгенерированный псевдоним, например order_items AS oi.",
sqlServerSpaceConfirmsCompletion: "Пробел подтверждает автодополнение (стиль SQL Server)",
+8
View File
@@ -7636,6 +7636,14 @@ export default withEnglishFallback({
sqlVariableSyntax_sqlserverDescription: "SQL Server biçemli yer tutucuları değiştirmek için değer sor (oturum değişkenleriyle çakışabilir).",
sqlVariableSyntax_atSet: "Satır içi değişken genişletme",
sqlVariableSyntax_atSetDescription: "Satır içi değişken bildirimlerini genişlet ve çalıştırmadan önce değerlerini yerine koy.",
tableCompletionSchemaQualification: "Tablo tamamlamasında şema nitelemesi",
tableCompletionSchemaQualificationDescription: "Tamamlanan tablo ve görünüm adlarının ne zaman şema içereceğini seçin.",
tableCompletionSchemaQualificationNever: "Hiçbir zaman",
tableCompletionSchemaQualificationNeverDescription: "Yalnızca tablo veya görünüm adını ekler.",
tableCompletionSchemaQualificationCollision: "Ad çakışmasında",
tableCompletionSchemaQualificationCollisionDescription: "Geçerli otomatik davranışı kullanır ve şemayı yalnızca gerektiğinde ekler.",
tableCompletionSchemaQualificationAlways: "Her zaman",
tableCompletionSchemaQualificationAlwaysDescription: "Şema meta verilerde bulunduğunda her zaman ekler.",
autoAliasTables: "Tablo takma adlarını otomatik ekle",
autoAliasTablesDescription: "FROM veya JOIN içinde bir tablo tamamlaması seçildiğinde order_items AS oi gibi üretilmiş bir takma ad ekle.",
sqlServerSpaceConfirmsCompletion: "Boşlukla tamamlamayı onayla (SQL Server tarzı)",
+8
View File
@@ -8376,6 +8376,14 @@ export default withEnglishFallback({
sqlVariableSyntax_sqlserverDescription: "为 SQL Server 风格占位符提示输入替换值(可能与会话变量冲突)。",
sqlVariableSyntax_atSet: "内联变量展开",
sqlVariableSyntax_atSetDescription: "执行前展开内联变量声明并就地代入其值。",
tableCompletionSchemaQualification: "SQL 表补全的模式限定",
tableCompletionSchemaQualificationDescription: "选择表和视图补全插入时是否带模式名。",
tableCompletionSchemaQualificationNever: "从不",
tableCompletionSchemaQualificationNeverDescription: "仅插入表名或视图名。",
tableCompletionSchemaQualificationCollision: "同名冲突时",
tableCompletionSchemaQualificationCollisionDescription: "保持当前自动行为,仅在需要时插入模式名。",
tableCompletionSchemaQualificationAlways: "始终",
tableCompletionSchemaQualificationAlwaysDescription: "元数据提供模式名时始终插入。",
autoAliasTables: "自动添加表别名",
autoAliasTablesDescription: "补全 FROM 或 JOIN 表名时自动添加别名。",
sqlServerSpaceConfirmsCompletion: "空格确认补全(SQL Server 习惯)",
+8
View File
@@ -6983,6 +6983,14 @@ export default withEnglishFallback({
sqlVariableSyntax_sqlserverDescription: "為 SQL Server 風格佔位符提示輸入替換值(可能與工作階段變數衝突)。",
sqlVariableSyntax_atSet: "內聯變數展開",
sqlVariableSyntax_atSetDescription: "執行前展開內聯變數宣告並就地代入其值。",
tableCompletionSchemaQualification: "SQL 資料表補全的結構描述限定",
tableCompletionSchemaQualificationDescription: "選擇資料表和檢視補全插入時是否帶結構描述名稱。",
tableCompletionSchemaQualificationNever: "永不",
tableCompletionSchemaQualificationNeverDescription: "僅插入資料表或檢視名稱。",
tableCompletionSchemaQualificationCollision: "同名衝突時",
tableCompletionSchemaQualificationCollisionDescription: "保持目前自動行為,僅在需要時插入結構描述名稱。",
tableCompletionSchemaQualificationAlways: "一律",
tableCompletionSchemaQualificationAlwaysDescription: "中繼資料提供結構描述名稱時一律插入。",
autoAliasTables: "自動加入資料表別名",
autoAliasTablesDescription: "在 FROM 或 JOIN 中選擇資料表補全時,自動插入類似 order_items AS oi 的資料表別名。",
sqlServerSpaceConfirmsCompletion: "空格確認補全(SQL Server 習慣)",
@@ -30,6 +30,7 @@ function semanticCompletion(markedSql: string, input: Partial<SqlCompletionProvi
databaseType: options.databaseType,
keywordCase: input.keywordCase,
autoAliasTables: input.autoAliasTables,
tableCompletionSchemaQualification: input.tableCompletionSchemaQualification,
});
return { sql, cursor, model, context, items };
}
@@ -1365,7 +1365,7 @@ describe("sqlCompletion scoped metadata ranking", () => {
expect(items.findIndex((item) => item.label === "ORDERS_10K")).toBeLessThan(items.findIndex((item) => item.label === "TABLE"));
});
it("qualifies same-name PostgreSQL tables from different schemas", () => {
it("uses on-collision qualification by default for same-name PostgreSQL tables", () => {
const sql = "SELECT * FROM shared";
const items = buildSqlCompletionItems(sql, sql.length, {
databaseType: "postgres",
@@ -1381,6 +1381,86 @@ describe("sqlCompletion scoped metadata ranking", () => {
expect(items.map((item) => item.apply).sort()).toEqual(["public.shared", "reporting.shared"]);
});
it("never qualifies table names while keeping colliding candidates distinct", () => {
const sql = "SELECT * FROM shared";
const items = buildSqlCompletionItems(sql, sql.length, {
databaseType: "postgres",
dialect: "postgres",
tables: [
{ name: "shared", schema: "public", type: "table", applyName: "public.shared" },
{ name: "shared", schema: "reporting", type: "view", applyName: "reporting.shared" },
],
columnsByTable: new Map(),
tableCompletionSchemaQualification: "never",
}).filter((item) => item.type === "table");
expect(items).toHaveLength(2);
expect(items.map((item) => item.apply)).toEqual(["shared", "shared"]);
expect(items.map((item) => item.detail).sort()).toEqual(["public.shared", "reporting.shared"]);
});
it("always qualifies tables and views when schema metadata is available", () => {
const sql = "SELECT * FROM ";
const items = buildSqlCompletionItems(sql, sql.length, {
databaseType: "postgres",
dialect: "postgres",
tables: [
{ name: "Order Details", schema: "Sales Data", type: "view" },
{ name: "scratch", type: "table" },
],
columnsByTable: new Map(),
tableCompletionSchemaQualification: "always",
}).filter((item) => item.type === "table");
expect(items.find((item) => item.label === "Order Details")?.apply).toBe('"Sales Data"."Order Details"');
expect(items.find((item) => item.label === "scratch")?.apply).toBe("scratch");
});
it.each([
["never", "customers"],
["always", "sales.customers"],
] as const)("applies the %s policy to foreign-key table suggestions", (tableCompletionSchemaQualification, expectedApply) => {
const sql = "SELECT * FROM sales.orders o JOIN cus";
const items = buildSqlCompletionItems(sql, sql.length, {
databaseType: "postgres",
dialect: "postgres",
tables: [
{ name: "orders", schema: "sales", type: "table" },
{ name: "customers", schema: "sales", type: "table" },
],
columnsByTable: new Map(),
foreignKeysByTable: new Map([["sales.orders", [{ name: "orders_customer_id_fkey", column: "customer_id", ref_table: "customers", ref_schema: "sales", ref_column: "id" }]]]),
tableCompletionSchemaQualification,
});
const customers = items.filter((item) => item.type === "table" && item.label === "customers");
expect(customers).toHaveLength(1);
expect(customers[0]?.apply).toBe(expectedApply);
expect(customers[0]?.detail).toContain("related by");
});
it("does not repeat an already typed quoted schema or SQL Server database/schema qualifier", () => {
const postgresSql = 'SELECT * FROM "Sales Data".Ord';
const postgresItems = buildSqlCompletionItems(postgresSql, postgresSql.length, {
databaseType: "postgres",
dialect: "postgres",
tables: [{ name: "Order Details", schema: "Sales Data", type: "table" }],
columnsByTable: new Map(),
tableCompletionSchemaQualification: "always",
});
const sqlServerSql = "SELECT * FROM Reporting.dbo.Ord";
const sqlServerItems = buildSqlCompletionItems(sqlServerSql, sqlServerSql.length, {
databaseType: "sqlserver",
dialect: "sqlserver",
tables: [{ name: "Order Details", database: "Reporting", schema: "dbo", type: "table" }],
columnsByTable: new Map(),
tableCompletionSchemaQualification: "always",
});
expect(postgresItems.find((item) => item.label === "Order Details")?.apply).toBe('"Order Details"');
expect(sqlServerItems.find((item) => item.label === "Order Details")?.apply).toBe("[Order Details]");
});
it("qualifies same-name tables for generic metadata providers", () => {
const sql = "SELECT * FROM orders";
const items = buildSqlCompletionItems(sql, sql.length, {
@@ -136,6 +136,10 @@ describe("EDITOR_SETTINGS_DRAFT_KEYS", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("completionTriggerMode");
});
it("includes tableCompletionSchemaQualification", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("tableCompletionSchemaQualification");
});
it("includes tableHoverLookupMode", () => {
expect(EDITOR_SETTINGS_DRAFT_KEYS).toContain("tableHoverLookupMode");
});
@@ -231,6 +235,11 @@ describe("editorSettingsDraftFromSettings", () => {
const draft = editorSettingsDraftFromSettings(makeSettings({ completionTriggerMode: "always" as unknown } as Partial<EditorSettings>));
expect(draft.completionTriggerMode).toBe("positional");
});
it("maps and normalizes tableCompletionSchemaQualification", () => {
expect(editorSettingsDraftFromSettings(makeSettings({ tableCompletionSchemaQualification: "always" })).tableCompletionSchemaQualification).toBe("always");
expect(editorSettingsDraftFromSettings(makeSettings({ tableCompletionSchemaQualification: "invalid" } as any)).tableCompletionSchemaQualification).toBe("collision");
});
});
describe("normalizeTableOpenPageSizeDraft", () => {
@@ -406,6 +415,16 @@ describe("editorSettingsPatchFromDraft", () => {
const patch = editorSettingsPatchFromDraft(draft, base);
expect(patch.completionTriggerMode).toBeUndefined();
});
it("includes tableCompletionSchemaQualification only when changed", () => {
const settings = makeSettings({ tableCompletionSchemaQualification: "collision" });
const draft = editorSettingsDraftFromSettings(settings);
const base = editorSettingsDraftFromSettings(settings);
expect(editorSettingsPatchFromDraft(draft, base).tableCompletionSchemaQualification).toBeUndefined();
draft.tableCompletionSchemaQualification = "never";
expect(editorSettingsPatchFromDraft(draft, base).tableCompletionSchemaQualification).toBe("never");
});
});
describe("EDITOR_SETTINGS_DRAFT_KEYS - tabLayout", () => {
@@ -295,6 +295,7 @@ describe("settings search", () => {
{ titleKey: "settings.sidebarObjectInfoMode", category: "navigation", targetId: "navigation" },
{ titleKey: "settings.insertSpaceAfterCompletion", category: "editor", targetId: "editor" },
{ titleKey: "settings.completionTriggerMode", category: "editor", targetId: "editor" },
{ titleKey: "settings.tableCompletionSchemaQualification", category: "editor", targetId: "editor" },
{ titleKey: "settings.autoAliasTables", category: "editor", targetId: "editor" },
{ titleKey: "settings.clickTableNavigationTarget", category: "navigation", targetId: "navigation" },
{ titleKey: "settings.prefillNewQueryWithSelect", category: "navigation", targetId: "navigation" },
@@ -68,6 +68,18 @@ describe("settingsTransfer", () => {
expect(result.error.detail).toContain("updateDownloadSource");
});
it("round-trips table completion schema qualification as an editor setting", () => {
const result = parseSettingsTransferFile(fileWith({ tableCompletionSchemaQualification: "always" }));
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.editorSettings.tableCompletionSchemaQualification).toBe("always");
expect(result.value.categories).toEqual(["editor"]);
const invalid = parseSettingsTransferFile(fileWith({ tableCompletionSchemaQualification: "sometimes" }));
expect(invalid.ok).toBe(false);
if (!invalid.ok) expect(invalid.error.detail).toContain("tableCompletionSchemaQualification");
});
it("rejects out-of-range numbers that the normalizer clamps", () => {
const result = parseSettingsTransferFile(fileWith({ sidebarIndent: 99999 }));
expect(result.ok).toBe(false);
@@ -4,6 +4,7 @@ import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
import { normalizeQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit";
import { normalizeExternalSqlEditorMaxMb } from "@/lib/sql/sqlFileOpen";
import { normalizeCompletionTriggerMode } from "@/lib/sql/sqlCompletionTriggerPolicy";
import { normalizeSqlTableCompletionSchemaQualification } from "@/lib/sql/sqlCompletionSchemaQualification";
import { normalizeTableHoverLookupMode } from "@/lib/editor/hoverTableLookup";
import { normalizeRedisKeyTemplates } from "@/lib/redis/redisKeyTemplates";
@@ -25,6 +26,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"showCurrentStatementFrame",
"showInsertValueHints",
"autoAliasTables",
"tableCompletionSchemaQualification",
"insertSpaceAfterCompletion",
"sqlServerSpaceConfirmsCompletion",
"sortCompletionColumnsAlphabetically",
@@ -158,6 +160,7 @@ function normalizedDraftValue(key: EditorSettingsDraftKey, value: unknown): unkn
if (key === "queryResultMaxRows") return normalizeQueryResultMaxRowsDraft(value);
if (key === "externalSqlEditorMaxMb") return normalizeExternalSqlEditorMaxMb(value);
if (key === "completionTriggerMode") return normalizeCompletionTriggerMode(value);
if (key === "tableCompletionSchemaQualification") return normalizeSqlTableCompletionSchemaQualification(value);
if (key === "tableHoverLookupMode") return normalizeTableHoverLookupMode(value);
if (key === "redisKeyTemplates") return normalizeRedisKeyTemplates(value);
if (key === "backgroundImage") return normalizeBackgroundImageSettings(value);
@@ -145,6 +145,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "editor-completion-spacing", category: "editor", titleKey: "settings.insertSpaceAfterCompletion", descriptionKey: "settings.insertSpaceAfterCompletionDescription", targetId: "editor" },
{ id: "editor-sqlserver-space-completion", category: "editor", titleKey: "settings.sqlServerSpaceConfirmsCompletion", descriptionKey: "settings.sqlServerSpaceConfirmsCompletionDescription", targetId: "editor", visible: sqlServerSpaceCompletionVisible },
{ id: "editor-completion-trigger-mode", category: "editor", titleKey: "settings.completionTriggerMode", descriptionKey: "settings.completionTriggerModeDescription", targetId: "editor" },
{ id: "editor-table-completion-schema-qualification", category: "editor", titleKey: "settings.tableCompletionSchemaQualification", descriptionKey: "settings.tableCompletionSchemaQualificationDescription", targetId: "editor" },
{ id: "editor-auto-alias", category: "editor", titleKey: "settings.autoAliasTables", descriptionKey: "settings.autoAliasTablesDescription", targetId: "editor" },
{ id: "editor-unsaved-close", category: "editor", titleKey: "settings.confirmUnsavedSqlClose", descriptionKey: "settings.confirmUnsavedSqlCloseDescription", targetId: "editor" },
{ id: "editor-app-close-unsaved-tabs", category: "editor", titleKey: "settings.appCloseUnsavedTabsMode", descriptionKey: "settings.appCloseUnsavedTabsModeDescription", targetId: "editor" },
@@ -83,6 +83,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
"showCurrentStatementFrame",
"showInsertValueHints",
"autoAliasTables",
"tableCompletionSchemaQualification",
"insertSpaceAfterCompletion",
"sqlServerSpaceConfirmsCompletion",
"sortCompletionColumnsAlphabetically",
+31 -12
View File
@@ -21,6 +21,7 @@ import { driverProfileCompletionObjects, driverProfileCompletionTableMetadata, d
import { DORIS_FUNCTION_DOCS, DORIS_FUNCTION_SIGNATURES } from "@/lib/sql/doris/functions";
import { rejectsAliasReferenceInHaving } from "@/lib/database/databaseFeatureSupport";
import { isOracleCompletionDatabase } from "@/lib/sql/oracleCompletionSession";
import { normalizeSqlTableCompletionSchemaQualification, type SqlTableCompletionSchemaQualification } from "@/lib/sql/sqlCompletionSchemaQualification";
export { DEFAULT_SQL_SNIPPETS, resolveSqlSnippetBodyForDatabase } from "@/lib/sql/sqlSnippetTemplates";
@@ -1616,6 +1617,7 @@ export interface SqlCompletionProviderInput {
keywordCase?: SqlKeywordCase;
functionCase?: SqlKeywordCase;
autoAliasTables?: boolean;
tableCompletionSchemaQualification?: SqlTableCompletionSchemaQualification;
quoteIdentifiers?: boolean;
}
@@ -1636,6 +1638,7 @@ export function buildSqlCompletionItems(
keywordCase?: SqlKeywordCase;
functionCase?: SqlKeywordCase;
autoAliasTables?: boolean;
tableCompletionSchemaQualification?: SqlTableCompletionSchemaQualification;
quoteIdentifiers?: boolean;
},
): SqlCompletionItem[] {
@@ -1768,8 +1771,9 @@ class SqlCompletionProvider {
if (!context.exclusiveColumnSuggestions && context.suggestTables) {
const autoAliasTables = !!this.input.autoAliasTables && context.autoAliasTableCompletions && !context.tableCompletionTargetAliasUnsafe && supportsTableAliases(this.databaseType);
this.items.push(...buildForeignKeyRelatedTableItems(context, completionTables, this.input.foreignKeysByTable, this.dialect, autoAliasTables, this.databaseType, this.input.currentSchema));
this.items.push(...buildTableItems(context, completionTables, this.dialect, autoAliasTables, context.referencedTables, this.databaseType, this.input.currentSchema));
const schemaQualification = normalizeSqlTableCompletionSchemaQualification(this.input.tableCompletionSchemaQualification);
this.items.push(...buildForeignKeyRelatedTableItems(context, completionTables, this.input.foreignKeysByTable, this.dialect, autoAliasTables, this.databaseType, this.input.currentSchema, schemaQualification));
this.items.push(...buildTableItems(context, completionTables, this.dialect, autoAliasTables, context.referencedTables, this.databaseType, this.input.currentSchema, schemaQualification));
if (this.databaseType === "clickhouse") {
this.items.push(...buildClickHouseFunctionItems(context.prefix, context.openingParenAfterCursor, "table"));
}
@@ -3932,27 +3936,30 @@ function resolveTableSchemaQualification(
databaseType: DatabaseType | undefined,
currentSchema: string | undefined,
schemasByTableName: Map<string, Set<string>>,
schemaQualificationMode: SqlTableCompletionSchemaQualification,
): { ambiguousTableName: boolean; schemaQualification: boolean; defaultApplyName: string } {
const oracleSchemaQualification = isOracleCompletionDatabase(databaseType) && table.schema && table.schema.toUpperCase() !== "PUBLIC" && (!currentSchema || normalizeIdentifierPart(table.schema) !== normalizeIdentifierPart(currentSchema));
// A bare table name is ambiguous when metadata contains the same name in multiple schemas.
// Keep Oracle's current-schema behavior, but qualify the generic/PostgreSQL/SQL Server paths.
// Collision mode keeps Oracle's current-schema behavior and qualifies the
// generic/PostgreSQL/SQL Server paths only when metadata is ambiguous.
const ambiguousTableName = !isOracleCompletionDatabase(databaseType) && (schemasByTableName.get(normalizeIdentifierPart(table.name))?.size ?? 0) > 1;
const schemaQualification = !!table.schema && (oracleSchemaQualification || ambiguousTableName);
const schemaQualification = !!table.schema && (schemaQualificationMode === "always" || (schemaQualificationMode === "collision" && (oracleSchemaQualification || ambiguousTableName)));
const defaultApplyName = schemaQualification ? `${quoteCompletionApplyIdentifier(table.schema!, dialect)}.${quoteCompletionApplyIdentifier(table.name, dialect)}` : quoteCompletionApplyIdentifier(table.name, dialect);
return { ambiguousTableName, schemaQualification, defaultApplyName };
}
function buildTableItems(
context: Pick<SqlCompletionContext, "prefix" | "qualifier">,
context: Pick<SqlCompletionContext, "prefix" | "qualifier" | "qualifierParts">,
tables: SqlCompletionTable[],
dialect?: SqlCompletionApplyDialect,
autoAliasTables = false,
referencedTables: SqlCompletionReferencedTable[] = [],
databaseType?: DatabaseType,
currentSchema?: string,
schemaQualificationMode: SqlTableCompletionSchemaQualification = "collision",
): SqlCompletionItem[] {
const { prefix } = context;
const qualifierSchema = context.qualifier?.split(".").filter(Boolean).pop();
const qualifierSchema = context.qualifierParts?.[context.qualifierParts.length - 1] ?? context.qualifier?.split(".").filter(Boolean).pop();
const existingAliases = new Set(referencedTables.map((ref) => ref.alias?.toLowerCase()).filter((alias): alias is string => !!alias));
const matchingTables = tables.filter((table) => matchesPrefix(table.name, prefix));
// Ambiguity is decided among prefix-matching tables only, matching prior behavior.
@@ -3960,11 +3967,19 @@ function buildTableItems(
return matchingTables
.map((table) => {
const qualifiedByContext = !!qualifierSchema && !!table.schema && normalizeIdentifierPart(qualifierSchema) === normalizeIdentifierPart(table.schema);
const { ambiguousTableName, defaultApplyName } = resolveTableSchemaQualification(table, dialect, databaseType, currentSchema, schemasByTableName);
const { ambiguousTableName, defaultApplyName } = resolveTableSchemaQualification(table, dialect, databaseType, currentSchema, schemasByTableName, schemaQualificationMode);
const unqualifiedApplyName = quoteCompletionApplyIdentifier(table.name, dialect);
const rawSuppliedApplyName = table.applyName?.trim();
const suppliedApplyName = dialect === "sqlserver" && rawSuppliedApplyName ? quoteCompletionApplyName(rawSuppliedApplyName, dialect) : rawSuppliedApplyName;
const suppliedApplyNameIsQualified = suppliedApplyName?.includes(".") === true;
const applyName = qualifiedByContext ? quoteCompletionApplyIdentifier(table.name, dialect) : ambiguousTableName && !!table.schema && (!suppliedApplyName || !suppliedApplyNameIsQualified) ? defaultApplyName : (suppliedApplyName ?? defaultApplyName);
const applyName =
qualifiedByContext || schemaQualificationMode === "never"
? unqualifiedApplyName
: schemaQualificationMode === "always" && table.schema
? defaultApplyName
: ambiguousTableName && table.schema && (!suppliedApplyName || !suppliedApplyNameIsQualified)
? defaultApplyName
: (suppliedApplyName ?? defaultApplyName);
const alias = autoAliasTables ? generateTableCompletionAlias(table.name, existingAliases) : "";
const schemaDetail = ambiguousTableName && table.schema ? `${table.schema}.${table.name}` : undefined;
const detail = table.detail && schemaDetail ? `${schemaDetail} ${table.detail}` : (table.detail ?? schemaDetail ?? (table.type === "table" ? undefined : table.type));
@@ -3974,7 +3989,7 @@ function buildTableItems(
detail,
apply: formatTableAliasApply(applyName, alias),
boost: computeBoost(table.name, prefix) + 1000 + (table.boost ?? 0),
dedupeKey: table.applyName || ambiguousTableName || (isOracleCompletionDatabase(databaseType) && table.schema) ? applyName : undefined,
dedupeKey: schemaQualificationMode === "never" && table.schema ? `${quoteCompletionApplyIdentifier(table.schema, dialect)}.${unqualifiedApplyName}` : table.applyName || ambiguousTableName || (isOracleCompletionDatabase(databaseType) && table.schema) ? applyName : undefined,
};
})
.sort(compareCompletionItems)
@@ -3989,6 +4004,7 @@ function buildForeignKeyRelatedTableItems(
autoAliasTables = false,
databaseType?: DatabaseType,
currentSchema?: string,
schemaQualificationMode: SqlTableCompletionSchemaQualification = "collision",
): SqlCompletionItem[] {
if (!foreignKeysByTable || context.referencedTables.length === 0) return [];
const candidates = new Map<string, { table: SqlCompletionTable; detail: string }>();
@@ -4016,11 +4032,14 @@ function buildForeignKeyRelatedTableItems(
// `schema.table` exactly when regular candidates would. Built from the same
// prefix-matching table set buildTableItems uses, keeping the two in lockstep.
const schemasByTableName = collectSchemasByTableName(tables.filter((table) => matchesPrefix(table.name, context.prefix)));
const qualifierSchema = context.qualifierParts?.[context.qualifierParts.length - 1] ?? context.qualifier?.split(".").filter(Boolean).pop();
return [...candidates.values()]
.map(({ table, detail }) => {
const { ambiguousTableName, defaultApplyName } = resolveTableSchemaQualification(table, dialect, databaseType, currentSchema, schemasByTableName);
const applyName = defaultApplyName;
const qualifiedByContext = !!qualifierSchema && !!table.schema && normalizeIdentifierPart(qualifierSchema) === normalizeIdentifierPart(table.schema);
const { ambiguousTableName, defaultApplyName } = resolveTableSchemaQualification(table, dialect, databaseType, currentSchema, schemasByTableName, schemaQualificationMode);
const unqualifiedApplyName = quoteCompletionApplyIdentifier(table.name, dialect);
const applyName = qualifiedByContext || schemaQualificationMode === "never" ? unqualifiedApplyName : defaultApplyName;
const alias = autoAliasTables ? generateTableCompletionAlias(table.name, existingAliases) : "";
return {
label: table.name,
@@ -4030,7 +4049,7 @@ function buildForeignKeyRelatedTableItems(
boost: computeBoost(table.name, context.prefix) + 3600,
// Mirror buildTableItems' dedupeKey so an FK candidate and the regular
// candidate for the same schema-qualified table collapse to one entry.
dedupeKey: ambiguousTableName || (isOracleCompletionDatabase(databaseType) && table.schema) ? applyName : undefined,
dedupeKey: schemaQualificationMode === "never" && table.schema ? `${quoteCompletionApplyIdentifier(table.schema, dialect)}.${unqualifiedApplyName}` : ambiguousTableName || (isOracleCompletionDatabase(databaseType) && table.schema) ? applyName : undefined,
};
})
.sort(compareCompletionItems);
@@ -0,0 +1,8 @@
export type SqlTableCompletionSchemaQualification = "never" | "collision" | "always";
export const DEFAULT_SQL_TABLE_COMPLETION_SCHEMA_QUALIFICATION: SqlTableCompletionSchemaQualification = "collision";
export function normalizeSqlTableCompletionSchemaQualification(value: unknown): SqlTableCompletionSchemaQualification {
if (value === "never" || value === "collision" || value === "always") return value;
return DEFAULT_SQL_TABLE_COMPLETION_SCHEMA_QUALIFICATION;
}
+23
View File
@@ -224,6 +224,13 @@ describe("normalizeEditorSettings", () => {
expect(normalizeEditorSettings({ autoAliasTables: false }).autoAliasTables).toBe(false);
});
it("defaults table completion schema qualification to collision and preserves valid modes", () => {
expect(normalizeEditorSettings({}).tableCompletionSchemaQualification).toBe("collision");
expect(normalizeEditorSettings({ tableCompletionSchemaQualification: "never" }).tableCompletionSchemaQualification).toBe("never");
expect(normalizeEditorSettings({ tableCompletionSchemaQualification: "always" }).tableCompletionSchemaQualification).toBe("always");
expect(normalizeEditorSettings({ tableCompletionSchemaQualification: "invalid" } as any).tableCompletionSchemaQualification).toBe("collision");
});
it("enables a trailing space after completion by default and preserves the opt-out", () => {
expect(normalizeEditorSettings({}).insertSpaceAfterCompletion).toBe(true);
expect(normalizeEditorSettings({ insertSpaceAfterCompletion: false }).insertSpaceAfterCompletion).toBe(false);
@@ -1134,6 +1141,22 @@ describe("settingsStore persisted settings initialization", () => {
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ dataGridKeepFilterEditorExpanded: false }));
});
it("persists table completion schema qualification updates", async () => {
const loadEditorSettings = vi.fn().mockResolvedValue({});
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();
saveEditorSettings.mockClear();
await store.updateEditorSettingsAndPersist({ tableCompletionSchemaQualification: "always" });
expect(store.editorSettings.tableCompletionSchemaQualification).toBe("always");
expect(saveEditorSettings).toHaveBeenLastCalledWith(expect.objectContaining({ tableCompletionSchemaQualification: "always" }));
});
it("loads and persists the substitution switch without discarding syntax overrides", async () => {
const loadEditorSettings = vi.fn().mockResolvedValue({
sqlVariableSubstitutionEnabled: false,
+24 -1
View File
@@ -33,12 +33,31 @@ import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, normalizeTableColumnTemplateField
import { type DataTabReuseMode, DEFAULT_DATA_TAB_REUSE_MODE, normalizeDataTabReuseMode } from "@/lib/tabs/dataTabReuseMode";
import { normalizeTableHoverLookupMode, type TableHoverLookupMode } from "@/lib/editor/hoverTableLookup";
import { normalizeCompletionTriggerMode, type SqlCompletionTriggerMode } from "@/lib/sql/sqlCompletionTriggerPolicy";
import { DEFAULT_SQL_TABLE_COMPLETION_SCHEMA_QUALIFICATION, normalizeSqlTableCompletionSchemaQualification, type SqlTableCompletionSchemaQualification } from "@/lib/sql/sqlCompletionSchemaQualification";
import { DEFAULT_CSV_QUOTE_MODE, normalizeCsvQuoteMode, type CsvQuoteMode } from "@/lib/export/csvQuoteMode";
import { configureMetadataRuntimeCache, METADATA_CACHE_DEFAULT_MEMORY_MB, normalizeMetadataCacheMemoryMb } from "@/lib/metadata/metadataRuntimeCache";
import type { AiApiStyle, AiAssistantMode, AiAuthMethod, AiChatSelectionState, AiConfig, AiConfigItem, AiConfiguredModel, AiEffortLevel, AiEffortSelection, AiModelEffortPreference, AiProvider, AiReasoningLevel, AiTestConnectionResult } from "@/types/ai";
import type { SqlShortcutAction, SqlSnippet, TableInfoTab } from "@/types/database";
export type { AiApiStyle, AiAuthMethod, AiChatSelectionState, AiConfig, AiConfigItem, AiConfiguredModel, AiEffortLevel, AiEffortSelection, AiProvider, AiReasoningLevel, AiTestConnectionResult, CsvQuoteMode, DataTabReuseMode, SavedSqlOpenTargetMode, SqlCompletionTriggerMode, TableHoverLookupMode };
export type {
AiApiStyle,
AiAuthMethod,
AiChatSelectionState,
AiConfig,
AiConfigItem,
AiConfiguredModel,
AiEffortLevel,
AiEffortSelection,
AiProvider,
AiReasoningLevel,
AiTestConnectionResult,
CsvQuoteMode,
DataTabReuseMode,
SavedSqlOpenTargetMode,
SqlCompletionTriggerMode,
SqlTableCompletionSchemaQualification,
TableHoverLookupMode,
};
export interface DesktopSettings {
show_tray_icon: boolean;
@@ -830,6 +849,7 @@ export interface EditorSettings {
showCurrentStatementFrame: boolean;
showInsertValueHints: boolean;
autoAliasTables: boolean;
tableCompletionSchemaQualification: SqlTableCompletionSchemaQualification;
insertSpaceAfterCompletion: boolean;
sqlServerSpaceConfirmsCompletion: boolean;
sortCompletionColumnsAlphabetically: boolean;
@@ -1114,6 +1134,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
showCurrentStatementFrame: true,
showInsertValueHints: true,
autoAliasTables: true,
tableCompletionSchemaQualification: DEFAULT_SQL_TABLE_COMPLETION_SCHEMA_QUALIFICATION,
insertSpaceAfterCompletion: true,
sqlServerSpaceConfirmsCompletion: false,
sortCompletionColumnsAlphabetically: true,
@@ -1676,6 +1697,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
showCurrentStatementFrame: typeof settings.showCurrentStatementFrame === "boolean" ? settings.showCurrentStatementFrame : DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame,
showInsertValueHints: typeof settings.showInsertValueHints === "boolean" ? settings.showInsertValueHints : DEFAULT_EDITOR_SETTINGS.showInsertValueHints,
autoAliasTables: settings.autoAliasTables ?? DEFAULT_EDITOR_SETTINGS.autoAliasTables,
tableCompletionSchemaQualification: normalizeSqlTableCompletionSchemaQualification(settings.tableCompletionSchemaQualification),
insertSpaceAfterCompletion: typeof settings.insertSpaceAfterCompletion === "boolean" ? settings.insertSpaceAfterCompletion : DEFAULT_EDITOR_SETTINGS.insertSpaceAfterCompletion,
sqlServerSpaceConfirmsCompletion: typeof settings.sqlServerSpaceConfirmsCompletion === "boolean" ? settings.sqlServerSpaceConfirmsCompletion : DEFAULT_EDITOR_SETTINGS.sqlServerSpaceConfirmsCompletion,
sortCompletionColumnsAlphabetically: typeof settings.sortCompletionColumnsAlphabetically === "boolean" ? settings.sortCompletionColumnsAlphabetically : DEFAULT_EDITOR_SETTINGS.sortCompletionColumnsAlphabetically,
@@ -2505,6 +2527,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.showCurrentStatementFrame !== undefined) editorSettings.value.showCurrentStatementFrame = partial.showCurrentStatementFrame === true;
if (partial.showInsertValueHints !== undefined) editorSettings.value.showInsertValueHints = partial.showInsertValueHints === true;
if (partial.autoAliasTables !== undefined) editorSettings.value.autoAliasTables = partial.autoAliasTables;
if (partial.tableCompletionSchemaQualification !== undefined) editorSettings.value.tableCompletionSchemaQualification = normalizeSqlTableCompletionSchemaQualification(partial.tableCompletionSchemaQualification);
if (partial.insertSpaceAfterCompletion !== undefined) editorSettings.value.insertSpaceAfterCompletion = partial.insertSpaceAfterCompletion === true;
if (partial.sqlServerSpaceConfirmsCompletion !== undefined) editorSettings.value.sqlServerSpaceConfirmsCompletion = partial.sqlServerSpaceConfirmsCompletion === true;
if (partial.sortCompletionColumnsAlphabetically !== undefined) editorSettings.value.sortCompletionColumnsAlphabetically = partial.sortCompletionColumnsAlphabetically === true;