feat(editor): include database name in generated SQL

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-08-22 13:40:42 +08:00
1 parent fcb6ea92db
commit a6f4cebd18
34 files changed
+379 -23

No files matched your search

+1
View File
@@ -1705,6 +1705,7 @@ async function newQuery() {
databaseType: effectiveDatabaseTypeForConnection(conn),
driverProfile: conn.driver_profile,
identifierQuote: connectionStore.connectionIdentifierQuote?.(target.connectionId),
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
});
const tabId = queryStore.createTab(conn.id, target.database, undefined, "query", target.schema, initialSql, target.catalog);
if (initialSql) {
@@ -444,6 +444,7 @@ const editDisconnectTabHandlingMode = ref<DisconnectTabHandlingMode>(settingsSto
const editDataTabReuseMode = ref<DataTabReuseMode>(settingsStore.editorSettings.dataTabReuseMode);
const editOpenDataTabsNextToActive = ref(settingsStore.editorSettings.openDataTabsNextToActive);
const editPrefillNewQueryWithSelect = ref(settingsStore.editorSettings.prefillNewQueryWithSelect);
const editGenerateSqlIncludeDatabaseName = ref(settingsStore.editorSettings.generateSqlIncludeDatabaseName);
const editClickTableNavigationTarget = ref<ClickTableNavigationTarget>(settingsStore.editorSettings.clickTableNavigationTarget);
const editUpdateNotificationsEnabled = ref(settingsStore.editorSettings.updateNotificationsEnabled);
const editSidebarHiddenTablePrefixes = ref(settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n"));
@@ -573,6 +574,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
dataTabReuseMode: editDataTabReuseMode.value,
openDataTabsNextToActive: editOpenDataTabsNextToActive.value,
prefillNewQueryWithSelect: editPrefillNewQueryWithSelect.value,
generateSqlIncludeDatabaseName: editGenerateSqlIncludeDatabaseName.value,
updateNotificationsEnabled: editUpdateNotificationsEnabled.value,
sidebarObjectInfoMode: editSidebarObjectInfoMode.value,
sidebarAllowHorizontalScroll: editSidebarAllowHorizontalScroll.value,
@@ -860,6 +862,7 @@ function syncEditorSettingsDraftFromStore() {
editDataTabReuseMode.value = settingsStore.editorSettings.dataTabReuseMode;
editOpenDataTabsNextToActive.value = settingsStore.editorSettings.openDataTabsNextToActive;
editPrefillNewQueryWithSelect.value = settingsStore.editorSettings.prefillNewQueryWithSelect;
editGenerateSqlIncludeDatabaseName.value = settingsStore.editorSettings.generateSqlIncludeDatabaseName;
editClickTableNavigationTarget.value = settingsStore.editorSettings.clickTableNavigationTarget;
editUpdateNotificationsEnabled.value = settingsStore.editorSettings.updateNotificationsEnabled;
editSidebarHiddenTablePrefixes.value = settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n");
@@ -1107,6 +1110,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editDataTabReuseMode.value = DEFAULT_EDITOR_SETTINGS.dataTabReuseMode;
editOpenDataTabsNextToActive.value = DEFAULT_EDITOR_SETTINGS.openDataTabsNextToActive;
editPrefillNewQueryWithSelect.value = DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect;
editGenerateSqlIncludeDatabaseName.value = DEFAULT_EDITOR_SETTINGS.generateSqlIncludeDatabaseName;
editClickTableNavigationTarget.value = DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
editSidebarObjectInfoMode.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode;
@@ -1229,6 +1233,7 @@ function resetAllDefaults() {
editDataTabReuseMode.value = DEFAULT_EDITOR_SETTINGS.dataTabReuseMode;
editOpenDataTabsNextToActive.value = DEFAULT_EDITOR_SETTINGS.openDataTabsNextToActive;
editPrefillNewQueryWithSelect.value = DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect;
editGenerateSqlIncludeDatabaseName.value = DEFAULT_EDITOR_SETTINGS.generateSqlIncludeDatabaseName;
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
editSidebarObjectInfoMode.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode;
editSidebarAllowHorizontalScroll.value = DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll;
@@ -4094,6 +4099,16 @@ onUnmounted(() => {
</div>
<Switch id="editor-prefill-new-query" v-model="editPrefillNewQueryWithSelect" class="mt-0.5" />
</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="generate-sql-include-database-name">{{ t("settings.generateSqlIncludeDatabaseName") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.generateSqlIncludeDatabaseNameDescription") }}
</p>
</div>
<Switch id="generate-sql-include-database-name" v-model="editGenerateSqlIncludeDatabaseName" class="mt-0.5" />
</div>
</div>
<Separator />
+23 -6
View File
@@ -83,7 +83,7 @@ import { formatElapsedSeconds } from "@/lib/common/elapsedTime";
import { dataGridCellDisplayText, dataGridCellEditorText } from "@/lib/dataGrid/dataGridCellCoercion";
import { createColumnDrafts } from "@/lib/table/tableStructureEditorState";
import type { BuildSingleColumnAlterSqlOptions } from "@/lib/table/tableStructureEditorSql";
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
import { buildTableSelectSql, qualifyTableReferencesInSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
import { uuid } from "@/lib/common/utils";
import { generateCellValues, type CellValueGenerationKind } from "@/lib/dataGrid/cellValueGeneration";
import { compactHeaderColumnType, isNumericColumnType, resolveDataGridTypeVisualKind, resolveHeaderColumnType, resolveResultColumnType } from "@/lib/dataGrid/dataGridColumnType";
@@ -4931,6 +4931,7 @@ async function fetchLargeValueRequestChunk(columnIndex: number, requests: LargeV
tableType: tableMeta.tableType,
catalog: tableMeta.catalog,
columns: selectedColumns,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
primaryKeys: tableMeta.primaryKeys,
whereInput: predicates.map((predicate) => `(${predicate})`).join(" OR "),
limit: requests.length,
@@ -6214,6 +6215,7 @@ async function applyOrderBySearch() {
tableName: tableMeta.tableName,
tableType: tableMeta.tableType,
columns: tableMeta.columns.map((column) => column.name),
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
primaryKeys: tableMeta.primaryKeys,
...tableDataLargeValuePreviewOptions(resolvedDatabaseType.value, tableMeta.columns, tableMeta.primaryKeys, pageSize.value),
orderBy: orderByClause,
@@ -6251,6 +6253,7 @@ async function applyWhereFilter() {
tableName: tableMeta.tableName,
tableType: tableMeta.tableType,
columns: tableMeta.columns.map((column) => column.name),
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
primaryKeys: tableMeta.primaryKeys,
...tableDataLargeValuePreviewOptions(resolvedDatabaseType.value, tableMeta.columns, tableMeta.primaryKeys, pageSize.value),
orderBy: orderByInput.value.trim() || (sortCol.value ? `${queryColumnRef(sortCol.value)} ${sortDir.value.toUpperCase()}` : undefined),
@@ -7331,11 +7334,23 @@ async function cancelActiveExport() {
const userFacingSql = ref("");
let userFacingSqlGeneration = 0;
function sqlWithDisplayDatabaseName(sql: string): string {
const database = props.tableMeta?.database ?? props.database;
if (!settingsStore.editorSettings.generateSqlIncludeDatabaseName || !database) return sql;
return qualifyTableReferencesInSql(sql, {
databaseType: resolvedDatabaseType.value,
database,
includeDatabaseName: true,
});
}
async function syncUserFacingSql() {
const generation = ++userFacingSqlGeneration;
const executionSql = props.sql?.trim() ?? "";
if (props.context !== "table-data" || !executionSql.includes("__DBX_LARGE_VALUE_BYTES_") || !props.tableMeta?.tableName) {
userFacingSql.value = executionSql;
const includeDatabaseName = settingsStore.editorSettings.generateSqlIncludeDatabaseName;
const shouldRebuildSql = executionSql.includes("__DBX_LARGE_VALUE_BYTES_") || includeDatabaseName;
if (props.context !== "table-data" || !shouldRebuildSql || !props.tableMeta?.tableName) {
userFacingSql.value = sqlWithDisplayDatabaseName(executionSql);
return;
}
@@ -7350,19 +7365,20 @@ async function syncUserFacingSql() {
schema: props.tableMeta.schema,
tableName: props.tableMeta.tableName,
tableType: props.tableMeta.tableType,
includeDatabaseName,
whereInput: currentWhereInput(),
orderBy: currentOrderBy(),
limit: props.pageLimit ?? pageSize.value,
offset: props.pageOffset ?? Math.max(0, currentPage.value - 1) * pageSize.value,
});
if (generation === userFacingSqlGeneration) userFacingSql.value = sql;
if (generation === userFacingSqlGeneration) userFacingSql.value = sqlWithDisplayDatabaseName(sql);
} catch {
if (generation === userFacingSqlGeneration) userFacingSql.value = executionSql;
if (generation === userFacingSqlGeneration) userFacingSql.value = sqlWithDisplayDatabaseName(executionSql);
}
}
watch(
() => [props.sql, props.context, props.tableMeta, props.pageLimit, props.pageOffset, currentWhereInput(), currentOrderBy()],
() => [props.sql, props.context, props.tableMeta, props.pageLimit, props.pageOffset, currentWhereInput(), currentOrderBy(), settingsStore.editorSettings.generateSqlIncludeDatabaseName],
() => void syncUserFacingSql(),
{ immediate: true },
);
@@ -10380,6 +10396,7 @@ async function loadForeignKeyDisplayLabels() {
schema,
tableName: config.refTable,
columns: [config.refColumn, config.displayColumn],
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
whereInput,
limit: batch.length,
});
@@ -1313,6 +1313,7 @@ async function openNewQuery(row: ObjectBrowserRow) {
database: props.database,
schema,
tableName: row.name,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
limit: 100,
}),
);
@@ -1656,6 +1656,7 @@ async function newQuery() {
catalog: node.catalog,
database: node.database,
schema: node.schema,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableName: node.label,
columns: [],
});
@@ -1738,6 +1739,7 @@ async function newSelectTemplate() {
catalog: context.node.catalog,
database: context.node.database,
schema: context.tableSchema,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableName: context.node.label,
columns: context.columns,
});
@@ -1758,6 +1760,7 @@ async function newInsertTemplate() {
catalog: context.node.catalog,
database: context.node.database,
schema: context.tableSchema,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableName: context.node.label,
columns: context.columns,
tableType: context.tableType,
@@ -1779,6 +1782,7 @@ async function newUpdateTemplate() {
catalog: context.node.catalog,
database: context.node.database,
schema: context.tableSchema,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableName: context.node.label,
columns: context.columns,
});
@@ -1799,6 +1803,7 @@ async function newDeleteTemplate() {
catalog: context.node.catalog,
database: context.node.database,
schema: context.tableSchema,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableName: context.node.label,
columns: context.columns,
});
@@ -80,6 +80,7 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
columns: realColumns?.map((column) => column.name),
primaryKeys,
...tableDataLargeValuePreviewOptions(effectiveDbType, realColumns ?? [], primaryKeys, limit),
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
includeRowId: useRowId,
limit,
...options,
@@ -118,6 +118,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
catalog: target.catalog,
database: target.database,
tableName: target.tableName,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableType: targetTableType,
columns: columns.map((column) => column.name),
primaryKeys,
@@ -162,6 +163,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
catalog: target.catalog,
database: target.database,
tableName: target.tableName,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableType: targetTableType,
columns: eagerColumns.map((column) => column.name),
primaryKeys: eagerPrimaryKeys,
@@ -209,6 +211,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
catalog: target.catalog,
database: target.database,
tableName: target.tableName,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableType: targetTableType,
whereInput: target.whereInput,
limit: 0,
@@ -254,6 +257,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
catalog: target.catalog,
database: target.database,
tableName: target.tableName,
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
tableType: targetTableType,
whereInput: target.whereInput,
primaryKeys,
@@ -347,6 +347,7 @@ export function useSidebarDataOpenRuntime() {
columns: columns.map((column) => column.name),
primaryKeys,
...tableDataLargeValuePreviewOptions(effectiveDbType, columns, primaryKeys, limit),
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
limit,
includeRowId,
});
+2
View File
@@ -6320,6 +6320,8 @@ export default {
savedSqlOpenTargetCurrentDescription: "Use the active tab’s connection, database, schema, and catalog when available; otherwise use the saved target.",
prefillNewQueryWithSelect: "Prefill new query with SELECT *",
prefillNewQueryWithSelectDescription: "When creating a new query, prefill the editor with SELECT * FROM <table> based on the active table tab or the table selected in the sidebar.",
generateSqlIncludeDatabaseName: "Include database name in generated SQL",
generateSqlIncludeDatabaseNameDescription: "When supported by the database, generated table SQL uses database.table instead of only the table name.",
clickTableNavigationTarget: "Ctrl+Click Table Opens DDL",
clickTableNavigationTargetDescription: "When enabled, Ctrl/Cmd+clicking a table name opens the table structure editor (DDL). When disabled, it opens the table data view.",
sqlVariableSyntax: "SQL variable & placeholder substitution",
+2
View File
@@ -6009,6 +6009,8 @@ export default withEnglishFallback({
savedSqlOpenTargetCurrentDescription: "Usar la conexión, la base de datos, el esquema y el catálogo de la pestaña activa cuando estén disponibles; de lo contrario, usar el destino guardado.",
prefillNewQueryWithSelect: "Rellenar nueva consulta con SELECT *",
prefillNewQueryWithSelectDescription: "Al crear una nueva consulta, rellena el editor con SELECT * FROM <tabla> según la pestaña de tabla activa o la tabla seleccionada en la barra lateral.",
generateSqlIncludeDatabaseName: "Incluir el nombre de la base de datos en el SQL generado",
generateSqlIncludeDatabaseNameDescription: "Cuando la base de datos lo admite, el SQL de tabla generado usa base_de_datos.tabla en lugar de solo el nombre de la tabla.",
clickTableNavigationTarget: "Ctrl+Clic abre DDL de tabla",
clickTableNavigationTargetDescription: "Cuando está activado, Ctrl/Cmd+clic en el nombre de la tabla abre el editor de estructura (DDL). Cuando está desactivado, abre la vista de datos.",
sqlVariableSyntax: "Sustitución de variables y marcadores SQL",
+2
View File
@@ -6009,6 +6009,8 @@ export default withEnglishFallback({
savedSqlOpenTargetCurrentDescription: "Usa la connessione, il database, lo schema e il catalogo della scheda attiva quando disponibili; altrimenti usa la destinazione salvata.",
prefillNewQueryWithSelect: "Precompila nuova query con SELECT *",
prefillNewQueryWithSelectDescription: "Alla creazione di una nuova query, precompila l'editor con SELECT * FROM <tabella> in base alla scheda tabella attiva o alla tabella selezionata nella barra laterale.",
generateSqlIncludeDatabaseName: "Includi il nome del database nell'SQL generato",
generateSqlIncludeDatabaseNameDescription: "Quando supportato dal database, l'SQL della tabella generato usa database.tabella anziché solo il nome della tabella.",
clickTableNavigationTarget: "Ctrl+Clic apre DDL tabella",
clickTableNavigationTargetDescription: "Quando attivato, Ctrl/Cmd+clic sul nome della tabella apre l'editor di struttura (DDL). Quando disattivato, apre la vista dati.",
sqlVariableSyntax: "Sostituzione di variabili e segnaposto SQL",
+2
View File
@@ -6031,6 +6031,8 @@ export default withEnglishFallback({
savedSqlOpenTargetCurrentDescription: "利用可能な場合は現在のタブの接続、データベース、スキーマ、カタログを使用し、それ以外は保存時の実行先を使用します。",
prefillNewQueryWithSelect: "新規クエリに SELECT を自動入力",
prefillNewQueryWithSelectDescription: "新規クエリ作成時、アクティブなテーブルタブまたはサイドバーで選択したテーブルに基づき、エディタに SELECT * FROM <テーブル名> を自動入力します。",
generateSqlIncludeDatabaseName: "生成 SQL にデータベース名を含める",
generateSqlIncludeDatabaseNameDescription: "データベースが対応している場合、生成するテーブル SQL でテーブル名のみではなく database.table を使用します。",
clickTableNavigationTarget: "Ctrl+クリックでテーブル構造を開く",
clickTableNavigationTargetDescription: "有効にすると、Ctrl/Cmd+クリックでテーブル構造エディタ(DDL)を開きます。無効の場合はテーブルデータビューを開きます。",
sqlVariableSyntax: "SQL 変数・プレースホルダー置換",
+2
View File
@@ -5737,6 +5737,8 @@ export default withEnglishFallback({
savedSqlOpenTargetCurrentDescription: "가능한 경우 현재 탭의 연결, 데이터베이스, 스키마 및 카탈로그를 사용하고, 그렇지 않으면 저장된 대상을 사용합니다.",
prefillNewQueryWithSelect: "새 쿼리에 SELECT * 미리 채우기",
prefillNewQueryWithSelectDescription: "새 쿼리를 만들 때 활성 테이블 탭이나 사이드바에서 선택한 테이블을 기반으로 편집기에 SELECT * FROM <table>을 미리 채웁니다.",
generateSqlIncludeDatabaseName: "생성된 SQL에 데이터베이스 이름 포함",
generateSqlIncludeDatabaseNameDescription: "데이터베이스가 지원하는 경우 생성된 테이블 SQL에서 테이블 이름만이 아니라 database.table을 사용합니다.",
sqlVariableSyntax: "SQL 변수 및 자리표시자 치환",
sqlVariableSyntaxDescription: "데이터베이스 유형별로 SQL을 실행하기 전에 DBX가 치환할 변수와 자리표시자 문법을 선택하세요. 모두 기본적으로 활성화됩니다.",
sqlVariableSubstitutionEnabled: "치환 사용",
+2
View File
@@ -6011,6 +6011,8 @@ export default withEnglishFallback({
savedSqlOpenTargetCurrentDescription: "Usar a conexão, o banco de dados, o esquema e o catálogo da aba ativa quando disponíveis; caso contrário, usar o destino salvo.",
prefillNewQueryWithSelect: "Preencher nova consulta com SELECT *",
prefillNewQueryWithSelectDescription: "Ao criar uma nova consulta, preenche o editor com SELECT * FROM <tabela> com base na aba de tabela ativa ou na tabela selecionada na barra lateral.",
generateSqlIncludeDatabaseName: "Incluir nome do banco de dados no SQL gerado",
generateSqlIncludeDatabaseNameDescription: "Quando compatível com o banco de dados, o SQL de tabela gerado usa banco.tabela em vez de somente o nome da tabela.",
clickTableNavigationTarget: "Ctrl+Clique abre DDL da tabela",
clickTableNavigationTargetDescription: "Quando ativado, Ctrl/Cmd+clicar no nome da tabela abre o editor de estrutura (DDL). Quando desativado, abre a visualização de dados.",
sqlVariableSyntax: "Substituição de variáveis e espaços reservados SQL",
+2
View File
@@ -6305,6 +6305,8 @@ export default withEnglishFallback({
savedSqlOpenTargetCurrentDescription: "优先使用当前标签页的连接、数据库、Schema 和 Catalog;不可用时回退到保存时的目标。",
prefillNewQueryWithSelect: "新建查询时预填充 SELECT 语句",
prefillNewQueryWithSelectDescription: "新建查询时,根据当前激活的数据表标签页或侧边栏选中的表,自动在编辑器中填充 SELECT * FROM <表名>。",
generateSqlIncludeDatabaseName: "生成 SQL 时包含数据库名",
generateSqlIncludeDatabaseNameDescription: "数据库支持时,生成的表 SQL 将使用“数据库名.表名”,而不只包含表名。",
clickTableNavigationTarget: "Ctrl+点击表名打开 DDL",
clickTableNavigationTargetDescription: "开启后,Ctrl/Cmd+点击表名将打开表结构编辑器(DDL);关闭则打开表数据视图。",
sqlVariableSyntax: "SQL 变量与占位符替换",
+2
View File
@@ -5330,6 +5330,8 @@ export default withEnglishFallback({
savedSqlOpenTargetCurrentDescription: "優先使用目前分頁的連線、資料庫、Schema 和 Catalog;無法使用時回復至儲存時的目標。",
prefillNewQueryWithSelect: "新建查詢時預填 SELECT 語句",
prefillNewQueryWithSelectDescription: "新建查詢時,根據目前啟用的資料表分頁或側邊欄選取的資料表,自動在編輯器中填入 SELECT * FROM <資料表名稱>。",
generateSqlIncludeDatabaseName: "產生 SQL 時包含資料庫名稱",
generateSqlIncludeDatabaseNameDescription: "資料庫支援時,產生的資料表 SQL 會使用「資料庫名稱.資料表名稱」,而非只包含資料表名稱。",
clickTableNavigationTarget: "Ctrl+點擊表名開啟 DDL",
clickTableNavigationTargetDescription: "開啟後,Ctrl/Cmd+點擊表名將開啟表結構編輯器(DDL);關閉則開啟表資料檢視。",
sqlVariableSyntax: "SQL 變數與佔位符替換",
@@ -5,7 +5,7 @@ const dataGridSource = readFileSync(new URL("../../../components/grid/DataGrid.v
describe("DataGrid context menu state", () => {
it("clears the context target whenever the result is replaced", () => {
expect(dataGridSource).toMatch(/watch\(\s*\(\) => props\.result,[\s\S]*?clearRowSelection\(\);\s*invalidateSyntheticContextSelection\(\);[\s\S]*?exitTransaction\(\);/);
expect(dataGridSource).toMatch(/watch\(\s*\(\) => props\.result,[\s\S]*?clearRowSelection\(\);\s*invalidateContextMenuTarget\(\);[\s\S]*?exitTransaction\(\);/);
});
it("uses the actual copyable row count in the copy menu", () => {
@@ -200,6 +200,10 @@ describe("buildSelectAllSql", () => {
expect(buildSelectAllSql("mysql", { schema: "mydb", tableName: "users" })).toBe("SELECT * FROM `users`");
});
it("includes the MySQL database when requested", () => {
expect(buildSelectAllSql("mysql", { database: "mydb", tableName: "users" }, undefined, undefined, true)).toBe("SELECT * FROM `mydb`.`users`");
});
it("qualifies and quotes a PostgreSQL table with its schema", () => {
expect(buildSelectAllSql("postgres", { schema: "public", tableName: "users" })).toBe('SELECT * FROM "public"."users"');
});
+86 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { qualifiedTableName, quoteTableDataIdentifier, quoteTableIdentifier } from "@/lib/table/tableSelectSql";
import { qualifiedTableName, qualifyTableReferencesInSql, quoteTableDataIdentifier, quoteTableIdentifier } from "@/lib/table/tableSelectSql";
describe("qualifiedTableName — Doris/StarRocks multi-catalog", () => {
it("prefixes external catalog for Doris (no schema)", () => {
@@ -28,6 +28,91 @@ describe("qualifiedTableName — Doris/StarRocks multi-catalog", () => {
});
});
describe("qualifiedTableName — optional database qualification", () => {
it("keeps MySQL table references unchanged unless enabled", () => {
expect(qualifiedTableName({ databaseType: "mysql", database: "analytics", tableName: "events" })).toBe("`events`");
expect(qualifiedTableName({ databaseType: "mysql", database: "analytics", tableName: "events", includeDatabaseName: true })).toBe("`analytics`.`events`");
});
it("uses the dialect's identifier quoting for database-qualified names", () => {
expect(qualifiedTableName({ databaseType: "clickhouse", database: "a`b", tableName: "c`d", includeDatabaseName: true })).toBe("`a``b`.`c``d`");
});
it("uses MySQL-compatible quoting for GoldenDB", () => {
expect(qualifiedTableName({ databaseType: "goldendb", database: "a`b", tableName: "c`d", includeDatabaseName: true })).toBe("`a``b`.`c``d`");
});
});
describe("qualifyTableReferencesInSql", () => {
it("qualifies FROM and JOIN sources while preserving aliases", () => {
expect(
qualifyTableReferencesInSql("SELECT * FROM apis AS ap JOIN users AS u ON u.id = ap.user_id", {
databaseType: "mysql",
database: "aaa",
includeDatabaseName: true,
}),
).toBe("SELECT * FROM `aaa`.`apis` AS ap JOIN `aaa`.`users` AS u ON u.id = ap.user_id");
});
it("qualifies every source in a multi-line LEFT JOIN query", () => {
const sql = "SELECT `u`.`id`, `g`.`group_name`\nFROM `users` AS `u`\nLEFT JOIN `group_users` AS `gu` ON `gu`.`user_id` = `u`.`id`\nLEFT JOIN `groups` AS `g` ON `g`.`id` = `gu`.`group_id`\nLEFT JOIN `user_roles` AS `ur` ON `ur`.`user_id` = `u`.`id`";
const result = qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true });
expect(result).toContain("FROM `aaa`.`users` AS `u`");
expect(result).toContain("LEFT JOIN `aaa`.`group_users` AS `gu`");
expect(result).toContain("LEFT JOIN `aaa`.`groups` AS `g`");
expect(result).toContain("LEFT JOIN `aaa`.`user_roles` AS `ur`");
});
it("leaves already-qualified sources unchanged", () => {
const options = { databaseType: "mysql" as const, database: "aaa", includeDatabaseName: true };
expect(qualifyTableReferencesInSql("SELECT * FROM `aaa`.`apis`", options)).toBe("SELECT * FROM `aaa`.`apis`");
});
it("qualifies physical tables inside CTEs but never CTE references", () => {
const sql = "WITH users AS (SELECT * FROM archived_users)\nSELECT * FROM users";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("WITH users AS (SELECT * FROM `aaa`.`archived_users`)\nSELECT * FROM users");
});
it("preserves CTE references when the statement has a trailing semicolon", () => {
const sql = "WITH users AS (SELECT * FROM archived_users) SELECT * FROM users;";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("WITH users AS (SELECT * FROM `aaa`.`archived_users`) SELECT * FROM users;");
});
it("keeps CTE visibility scoped to each statement", () => {
const sql = "WITH users AS (SELECT * FROM archived_users) SELECT * FROM users; WITH roles AS (SELECT * FROM archived_roles) SELECT * FROM roles;";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("WITH users AS (SELECT * FROM `aaa`.`archived_users`) SELECT * FROM users; WITH roles AS (SELECT * FROM `aaa`.`archived_roles`) SELECT * FROM roles;");
});
it("does not treat a later CTE name as visible inside an earlier CTE", () => {
const sql = "WITH first AS (SELECT * FROM second), second AS (SELECT * FROM archived_users) SELECT * FROM first";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("WITH first AS (SELECT * FROM `aaa`.`second`), second AS (SELECT * FROM `aaa`.`archived_users`) SELECT * FROM first");
});
it("does not leak a nested CTE name into the outer query", () => {
const sql = "SELECT * FROM (WITH users AS (SELECT * FROM archived_users) SELECT * FROM users) nested JOIN users ON 1 = 1";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("SELECT * FROM (WITH users AS (SELECT * FROM `aaa`.`archived_users`) SELECT * FROM users) nested JOIN `aaa`.`users` ON 1 = 1");
});
it("keeps outer CTEs visible inside nested WITH queries", () => {
const sql = "WITH users AS (SELECT * FROM archived_users) SELECT * FROM (WITH roles AS (SELECT * FROM users) SELECT * FROM roles) nested";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("WITH users AS (SELECT * FROM `aaa`.`archived_users`) SELECT * FROM (WITH roles AS (SELECT * FROM users) SELECT * FROM roles) nested");
});
it("does not let a nested CTE suppress an earlier outer physical table", () => {
const sql = "SELECT * FROM users WHERE EXISTS (WITH users AS (SELECT * FROM archived_users) SELECT * FROM users)";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("SELECT * FROM `aaa`.`users` WHERE EXISTS (WITH users AS (SELECT * FROM `aaa`.`archived_users`) SELECT * FROM users)");
});
it("does not rewrite FROM and JOIN text inside literals or comments", () => {
const sql = "SELECT 'FROM users JOIN groups' AS note\nFROM users -- JOIN groups";
expect(qualifyTableReferencesInSql(sql, { databaseType: "mysql", database: "aaa", includeDatabaseName: true })).toBe("SELECT 'FROM users JOIN groups' AS note\nFROM `aaa`.`users` -- JOIN groups");
});
it("uses GoldenDB's MySQL-compatible identifier quoting", () => {
expect(qualifyTableReferencesInSql("SELECT * FROM users", { databaseType: "goldendb", database: "aaa", includeDatabaseName: true })).toBe("SELECT * FROM `aaa`.`users`");
});
});
describe("qualifiedTableName — SQLite attached databases", () => {
it("qualifies tables with the attached database alias", () => {
expect(qualifiedTableName({ databaseType: "sqlite", schema: "analytics", tableName: "events" })).toBe('"analytics"."events"');
+1 -1
View File
@@ -1291,7 +1291,7 @@ export async function buildDroppedFilePreviewSql(options: DroppedFilePreviewSqlO
}
export async function buildTableSelectSql(options: BuildTableSelectSqlOptions): Promise<string> {
return post("/api/query/build-table-select-sql", { options });
return post("/api/query/build-table-select-sql", { options, includeDatabaseName: options.includeDatabaseName === true });
}
export async function buildDatabaseSearchSql(options: DatabaseSearchSqlOptions): Promise<DatabaseSearchSql | null> {
+1 -1
View File
@@ -1452,7 +1452,7 @@ export async function buildDroppedFilePreviewSql(options: DroppedFilePreviewSqlO
}
export async function buildTableSelectSql(options: BuildTableSelectSqlOptions): Promise<string> {
return invoke("build_table_select_sql", { options });
return invoke("build_table_select_sql", { options, includeDatabaseName: options.includeDatabaseName === true });
}
export async function buildDatabaseSearchSql(options: DatabaseSearchSqlOptions): Promise<DatabaseSearchSql | null> {
@@ -186,6 +186,7 @@ describe("settings search", () => {
{ titleKey: "settings.completionTriggerMode", category: "editor", targetId: "editor" },
{ titleKey: "settings.autoAliasTables", category: "editor", targetId: "editor" },
{ titleKey: "settings.clickTableNavigationTarget", category: "editor", targetId: "editor" },
{ titleKey: "settings.generateSqlIncludeDatabaseName", category: "editor", targetId: "editor" },
{ titleKey: "settings.sqlFormatterKeywordCase", category: "formatter", targetId: "formatter" },
{ titleKey: "settings.sqlFormatterFunctionCase", category: "formatter", targetId: "formatter" },
{ titleKey: "settings.sqlFormatterDataTypeCase", category: "formatter", targetId: "formatter" },
@@ -65,6 +65,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"dataTabReuseMode",
"openDataTabsNextToActive",
"prefillNewQueryWithSelect",
"generateSqlIncludeDatabaseName",
"updateNotificationsEnabled",
"sidebarObjectInfoMode",
"sidebarAllowHorizontalScroll",
@@ -120,6 +120,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ 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" },
{ id: "editor-prefill-query", category: "editor", titleKey: "settings.prefillNewQueryWithSelect", descriptionKey: "settings.prefillNewQueryWithSelectDescription", targetId: "editor" },
{ id: "editor-generate-sql-include-database", category: "editor", titleKey: "settings.generateSqlIncludeDatabaseName", descriptionKey: "settings.generateSqlIncludeDatabaseNameDescription", targetId: "editor" },
{ id: "editor-diagnostics", category: "editor", titleKey: "settings.sqlSemanticDiagnosticsEnabled", descriptionKey: "settings.sqlSemanticDiagnosticsEnabledDescription", targetId: "editor" },
{ id: "editor-sql-variables", category: "editor", titleKey: "settings.sqlVariableSyntax", descriptionKey: "settings.sqlVariableSyntaxDescription", targetId: "editor" },
{ id: "editor-saved-sql-target", category: "editor", titleKey: "settings.savedSqlOpenTarget", targetId: "editor" },
+4 -3
View File
@@ -92,6 +92,7 @@ export interface ResolveNewQueryInitialSqlInput extends ResolveNewQueryTableInpu
databaseType?: DatabaseType;
driverProfile?: string;
identifierQuote?: string;
includeDatabaseName?: boolean;
}
// Database types whose "table" view does not use standard SQL `SELECT * FROM <table>`
@@ -147,9 +148,9 @@ export function resolveNewQueryTable(input: ResolveNewQueryTableInput): NewQuery
* the same per-dialect identifier quoting and schema/catalog qualification used
* by the table-data view.
*/
export function buildSelectAllSql(databaseType: DatabaseType | undefined, table: Pick<NewQueryTable, "schema" | "catalog" | "tableName"> & Partial<Pick<NewQueryTable, "database">>, identifierQuote?: string, driverProfile?: string): string {
export function buildSelectAllSql(databaseType: DatabaseType | undefined, table: Pick<NewQueryTable, "schema" | "catalog" | "tableName"> & Partial<Pick<NewQueryTable, "database">>, identifierQuote?: string, driverProfile?: string, includeDatabaseName = false): string {
if (databaseType === "victoriametrics") return metricRangeQuery(table.tableName);
const ref = qualifiedTableName({ databaseType, driverProfile, identifierQuote, database: table.database, schema: table.schema, catalog: table.catalog, tableName: table.tableName });
const ref = qualifiedTableName({ databaseType, driverProfile, identifierQuote, database: table.database, schema: table.schema, catalog: table.catalog, tableName: table.tableName, includeDatabaseName });
return `SELECT * FROM ${ref}`;
}
@@ -164,5 +165,5 @@ export function resolveNewQueryInitialSql(input: ResolveNewQueryInitialSqlInput)
const table = resolveNewQueryTable(input);
if (!table || table.connectionId !== input.targetConnectionId || table.database !== input.targetDatabase) return undefined;
return buildSelectAllSql(input.databaseType, table, input.identifierQuote, input.driverProfile);
return buildSelectAllSql(input.databaseType, table, input.identifierQuote, input.driverProfile, input.includeDatabaseName);
}
+134 -3
View File
@@ -4,6 +4,10 @@ import { jdbcDriverProfileUsesSchemaQualification } from "@/lib/database/jdbcDia
import * as api from "@/lib/backend/api.ts";
import { parseSqlServerLinkedSchema, sqlServerLinkedTableName } from "@/lib/database/sqlServerLinkedServers.ts";
import { isExplicitlyQuotedSqlIdentifier, quoteGaussDbJdbcIdentifier } from "@/lib/sql/sqlIdentifier.ts";
import { sqlSemanticDialectFor } from "@/lib/sql/semantic/dialect";
import { sqlSemanticTableNameSpans } from "@/lib/sql/semantic/model";
import { tokenIsIdentifier, tokenizeSqlSemantic, unquoteSqlSemanticIdentifier } from "@/lib/sql/semantic/tokens";
import type { SqlSemanticToken } from "@/lib/sql/semantic/types";
export interface BuildTableSelectSqlOptions {
databaseType?: DatabaseType;
@@ -25,6 +29,30 @@ export interface BuildTableSelectSqlOptions {
includeRowId?: boolean;
catalog?: string;
database?: string;
/** Include the active database when this dialect supports `database.table` references. */
includeDatabaseName?: boolean;
}
const DATABASE_QUALIFIED_TABLE_TYPES = new Set<DatabaseType>(["mysql", "clickhouse", "doris", "starrocks", "goldendb"]);
function sqlStatementSpans(sql: string, dialectId: string): Array<{ start: number; end: number }> {
const spans: Array<{ start: number; end: number }> = [];
let start = 0;
const appendSpan = (end: number) => {
let trimmedStart = start;
let trimmedEnd = end;
while (trimmedStart < trimmedEnd && /\s/.test(sql[trimmedStart] ?? "")) trimmedStart += 1;
while (trimmedEnd > trimmedStart && /\s/.test(sql[trimmedEnd - 1] ?? "")) trimmedEnd -= 1;
if (trimmedStart < trimmedEnd) spans.push({ start: trimmedStart, end: trimmedEnd });
};
for (const token of tokenizeSqlSemantic(sql, dialectId)) {
if (token.kind !== "punctuation" || token.text !== ";" || token.depth !== 0) continue;
appendSpan(token.span.start);
start = token.span.end;
}
appendSpan(sql.length);
return spans;
}
export function quoteTableIdentifier(databaseType: DatabaseType | undefined, name: string): string {
@@ -49,7 +77,8 @@ export function quoteTableIdentifier(databaseType: DatabaseType | undefined, nam
databaseType === "tdengine" ||
databaseType === "access" ||
databaseType === "doris" ||
databaseType === "starrocks"
databaseType === "starrocks" ||
databaseType === "goldendb"
)
return `\`${name.replace(/`/g, "``")}\``;
if (databaseType === "informix" && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(name)) return name;
@@ -75,8 +104,8 @@ function quoteCypherIdentifier(name: string): string {
return `\`${name.replace(/`/g, "``")}\``;
}
export function qualifiedTableName(options: Pick<BuildTableSelectSqlOptions, "databaseType" | "driverProfile" | "identifierQuote" | "schema" | "tableName" | "catalog" | "database">): string {
const { databaseType, driverProfile, identifierQuote, schema, tableName, catalog, database } = options;
export function qualifiedTableName(options: Pick<BuildTableSelectSqlOptions, "databaseType" | "driverProfile" | "identifierQuote" | "schema" | "tableName" | "catalog" | "database" | "includeDatabaseName">): string {
const { databaseType, driverProfile, identifierQuote, schema, tableName, catalog, database, includeDatabaseName } = options;
if (databaseType === "informix" && driverProfile?.trim().toLowerCase() === "gbase8s") {
return quoteTableDataIdentifier(databaseType, tableName, identifierQuote);
}
@@ -143,9 +172,111 @@ export function qualifiedTableName(options: Pick<BuildTableSelectSqlOptions, "da
}
return `${quoteTableIdentifier(databaseType, schema)}.${quoteTableIdentifier(databaseType, tableName)}`;
}
// MySQL-style engines use the selected database as their table namespace.
// Keep this opt-in so existing generated SQL remains unchanged by default.
const trimmedDatabase = database?.trim();
if (includeDatabaseName && trimmedDatabase && databaseType && DATABASE_QUALIFIED_TABLE_TYPES.has(databaseType)) {
return `${quoteTableIdentifier(databaseType, trimmedDatabase)}.${quoteTableIdentifier(databaseType, tableName)}`;
}
return quoteTableIdentifier(databaseType, tableName);
}
interface SqlCteVisibility {
name: string;
visibleFrom: number;
visibleUntil: number;
}
function matchingSqlParenthesisToken(tokens: readonly SqlSemanticToken[], openIndex: number): number {
const open = tokens[openIndex];
if (open?.text !== "(") return -1;
for (let index = openIndex + 1; index < tokens.length; index += 1) {
if (tokens[index]?.text === ")" && tokens[index]?.depth === open.depth) return index;
}
return -1;
}
function sqlCteVisibilities(tokens: readonly SqlSemanticToken[], sqlLength: number): SqlCteVisibility[] {
const visibilities: SqlCteVisibility[] = [];
for (let withIndex = 0; withIndex < tokens.length; withIndex += 1) {
const withToken = tokens[withIndex];
if (withToken?.kind !== "word" || withToken.normalized !== "with") continue;
const depth = withToken.depth;
const scopeEnd = tokens.find((token, index) => index > withIndex && token.depth < depth)?.span.start ?? sqlLength;
let index = withIndex + 1;
if (tokens[index]?.depth === depth && tokens[index]?.normalized === "recursive") index += 1;
while (index < tokens.length) {
while (tokens[index]?.depth === depth && tokens[index]?.text === ",") index += 1;
const nameToken = tokens[index];
if (!nameToken || nameToken.depth !== depth || !tokenIsIdentifier(nameToken)) break;
index += 1;
if (tokens[index]?.depth === depth && tokens[index]?.text === "(") {
const columnsClose = matchingSqlParenthesisToken(tokens, index);
if (columnsClose < 0) break;
index = columnsClose + 1;
}
if (tokens[index]?.depth === depth && tokens[index]?.normalized === "as") index += 1;
if (tokens[index]?.depth !== depth || tokens[index]?.text !== "(") break;
const bodyOpen = index;
const bodyClose = matchingSqlParenthesisToken(tokens, bodyOpen);
if (bodyClose < 0) break;
visibilities.push({
name: unquoteSqlSemanticIdentifier(nameToken),
visibleFrom: tokens[bodyOpen]!.span.end,
visibleUntil: scopeEnd,
});
index = bodyClose + 1;
if (tokens[index]?.depth !== depth || tokens[index]?.text !== ",") break;
}
}
return visibilities;
}
/**
* Qualifies physical table sources shown in a result footer without changing
* the SQL that was actually executed. The semantic model deliberately skips
* CTE names, strings, and comments that can happen to contain FROM/JOIN text.
*/
export function qualifyTableReferencesInSql(sql: string, options: Pick<BuildTableSelectSqlOptions, "databaseType" | "database" | "includeDatabaseName">): string {
if (!options.includeDatabaseName || !options.databaseType || !DATABASE_QUALIFIED_TABLE_TYPES.has(options.databaseType) || !options.database?.trim()) return sql;
const database = quoteTableIdentifier(options.databaseType, options.database.trim());
// Build replacements from right to left so that every semantic span still
// points at the original source text. Only one-part physical table names
// need the active database prefix; CTEs and already-qualified tables do not.
const semanticOptions = {
databaseType: options.databaseType,
dialect: options.databaseType === "goldendb" ? "mysql" : undefined,
} as const;
const dialectId = sqlSemanticDialectFor(semanticOptions).id;
const replacements = sqlStatementSpans(sql, dialectId)
.flatMap(({ start, end }) => {
const statementSql = sql.slice(start, end);
const tokens = tokenizeSqlSemantic(statementSql, dialectId);
const cteVisibilities = sqlCteVisibilities(tokens, statementSql.length);
const isCteReference = (name: string, span: { start: number; end: number }): boolean => cteVisibilities.some((cte) => cte.name.toLowerCase() === name.toLowerCase() && span.start >= cte.visibleFrom && span.end <= cte.visibleUntil);
const tokensBySpan = new Map(tokens.map((token) => [`${token.span.start}:${token.span.end}`, token]));
return sqlSemanticTableNameSpans(statementSql, semanticOptions)
.map((span) => ({ span, token: tokensBySpan.get(`${span.start}:${span.end}`) }))
.filter(({ span, token }) => {
if (!token || isCteReference(unquoteSqlSemanticIdentifier(token), span)) return false;
// sqlSemanticTableNameSpans returns the final segment in a qualified
// name, so a preceding dot identifies a database-qualified source.
return !statementSql.slice(0, span.start).trimEnd().endsWith(".");
})
.map(({ span, token }) => ({
span: { start: start + span.start, end: start + span.end },
tableName: unquoteSqlSemanticIdentifier(token!),
}));
})
.filter(({ span }, index, all) => all.findIndex((candidate) => candidate.span.start === span.start && candidate.span.end === span.end) === index)
.sort((left, right) => right.span.start - left.span.start);
return replacements.reduce((qualifiedSql, { span, tableName }) => `${qualifiedSql.slice(0, span.start)}${database}.${quoteTableIdentifier(options.databaseType, tableName)}${qualifiedSql.slice(span.end)}`, sql);
}
export function metricSelector(metricName: string): string {
const escaped = metricName.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n");
return `{__name__="${escaped}"}`;
@@ -8,6 +8,7 @@ export interface TableSqlTemplateOptions {
schema?: string;
catalog?: string;
database?: string;
includeDatabaseName?: boolean;
tableName: string;
columns?: ColumnInfo[];
tableType?: string;
@@ -105,6 +106,7 @@ function templateTableName(options: TableSqlTemplateOptions): string {
identifierQuote: options.identifierQuote,
catalog: options.catalog,
database: options.database,
includeDatabaseName: options.includeDatabaseName,
schema: options.schema,
tableName: options.tableName,
});
+1
View File
@@ -2898,6 +2898,7 @@ export const useQueryStore = defineStore("query", () => {
columns: tableMeta.columns.map((column) => column.name),
primaryKeys,
...tableDataLargeValuePreviewOptions(effectiveDbType, tableMeta.columns, primaryKeys, limit),
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
includeRowId: usesSyntheticRowIdKey(effectiveDbType, primaryKeys, tableMeta.tableType),
whereInput: tab.whereInput,
orderBy,
+4
View File
@@ -601,6 +601,7 @@ export interface EditorSettings {
dataTabReuseMode: DataTabReuseMode;
openDataTabsNextToActive: boolean;
prefillNewQueryWithSelect: boolean;
generateSqlIncludeDatabaseName: boolean;
updateNotificationsEnabled: boolean;
sidebarHiddenTablePrefixes: string[];
sidebarObjectInfoMode: SidebarObjectInfoMode;
@@ -807,6 +808,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
dataTabReuseMode: DEFAULT_DATA_TAB_REUSE_MODE,
openDataTabsNextToActive: false,
prefillNewQueryWithSelect: true,
generateSqlIncludeDatabaseName: false,
updateNotificationsEnabled: true,
sidebarHiddenTablePrefixes: [],
sidebarObjectInfoMode: "comment-inline",
@@ -1199,6 +1201,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
),
openDataTabsNextToActive: typeof settings.openDataTabsNextToActive === "boolean" ? settings.openDataTabsNextToActive : DEFAULT_EDITOR_SETTINGS.openDataTabsNextToActive,
prefillNewQueryWithSelect: typeof settings.prefillNewQueryWithSelect === "boolean" ? settings.prefillNewQueryWithSelect : DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect,
generateSqlIncludeDatabaseName: settings.generateSqlIncludeDatabaseName === true,
updateNotificationsEnabled: settings.updateNotificationsEnabled ?? DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled,
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(settings.sidebarHiddenTablePrefixes),
sidebarObjectInfoMode: normalizeSidebarObjectInfoMode(
@@ -1788,6 +1791,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.dataTabReuseMode !== undefined) editorSettings.value.dataTabReuseMode = normalizeDataTabReuseMode(partial.dataTabReuseMode);
if (partial.openDataTabsNextToActive !== undefined) editorSettings.value.openDataTabsNextToActive = partial.openDataTabsNextToActive === true;
if (partial.prefillNewQueryWithSelect !== undefined) editorSettings.value.prefillNewQueryWithSelect = partial.prefillNewQueryWithSelect;
if (partial.generateSqlIncludeDatabaseName !== undefined) editorSettings.value.generateSqlIncludeDatabaseName = partial.generateSqlIncludeDatabaseName === true;
if (partial.updateNotificationsEnabled !== undefined) editorSettings.value.updateNotificationsEnabled = partial.updateNotificationsEnabled;
if (partial.sidebarHiddenTablePrefixes !== undefined) editorSettings.value.sidebarHiddenTablePrefixes = normalizeSidebarHiddenTablePrefixes(partial.sidebarHiddenTablePrefixes);
if (partial.sidebarObjectInfoMode !== undefined) editorSettings.value.sidebarObjectInfoMode = normalizeSidebarObjectInfoMode(partial.sidebarObjectInfoMode);