diff --git a/apps/desktop/src/components/grid/DataGridExtractorDialog.vue b/apps/desktop/src/components/grid/DataGridExtractorDialog.vue
index 1ea6c5e255..006e50433e 100644
--- a/apps/desktop/src/components/grid/DataGridExtractorDialog.vue
+++ b/apps/desktop/src/components/grid/DataGridExtractorDialog.vue
@@ -211,9 +211,7 @@ function save() {
-
+
diff --git a/apps/desktop/src/components/grid/__tests__/DataGridExtractorDialog.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridExtractorDialog.spec.ts
index a526339337..925bf9ba08 100644
--- a/apps/desktop/src/components/grid/__tests__/DataGridExtractorDialog.spec.ts
+++ b/apps/desktop/src/components/grid/__tests__/DataGridExtractorDialog.spec.ts
@@ -103,6 +103,20 @@ describe("extractor draft preview", () => {
expect(host.querySelector("pre")?.textContent).toBe("initial");
});
+ it("offers the database-name option for SQL updates as well (#11195)", async () => {
+ const { props, host, save } = mountDialog();
+ props.preference = "sql-updates";
+ props.items = [{ value: "sql-updates", label: "UPDATE" }];
+ props.open = true;
+ await advancePreview();
+
+ // 勾选后保存应带上 includeDatabaseName(复制为 UPDATE 也支持带库名/模式名)
+ await toggle(host, "copyExtractorIncludeDatabaseName");
+ const button = Array.from(host.querySelectorAll("button")).find((item) => item.textContent?.includes("common.save"))!;
+ button.click();
+ expect(save).toHaveBeenCalledWith(expect.objectContaining({ options: expect.objectContaining({ sql: expect.objectContaining({ includeDatabaseName: true }) }) }));
+ });
+
it("previews unsaved quote and temporal options without mutating saved settings", async () => {
const { props, host, preview, save } = mountDialog();
props.open = true;
diff --git a/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts b/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts
index 8cd808e8e6..0abcecf613 100644
--- a/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts
+++ b/apps/desktop/src/composables/__tests__/useDataGridExport.spec.ts
@@ -1289,6 +1289,22 @@ describe("useDataGridExport prepared row statements", () => {
expect(vi.mocked(extractDataGridSelection).mock.lastCall?.[0].options.sql).toEqual(saved.sql);
});
+ it("follows the extractor option for the copied SQL table qualifier (#11195)", async () => {
+ const tableMeta: DataGridTableMeta = { ...editableTable, schema: "SYSDBA" };
+ const matrix: CellSelectionMatrix = { rowIndexes: [0], columnIndexes: [0, 1], columns: ["id", "name"], rows: [[1, "Ada"]] };
+ vi.mocked(extractDataGridSelection).mockResolvedValue({ text: "copied", mimeType: "text/plain", fileExtension: "sql", rowCount: 1, columnCount: 2 });
+ const withDatabaseName = { ...DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, sql: { ...DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS.sql, includeDatabaseName: true } };
+
+ const enabled = createExportState(tableMeta, ["id", "name"], matrix, [1, "Ada"], undefined, matrix.rows, [], withDatabaseName, false, undefined, false, undefined, undefined, "dameng");
+ await enabled.copyWithExtractor("sql-inserts");
+ expect(vi.mocked(extractDataGridSelection).mock.lastCall?.[0].tableMeta).toMatchObject({ schema: "SYSDBA", tableName: "users" });
+
+ // 未勾选(默认)时保持既有行为:把可选的库名/模式名前缀交给后端省略
+ const disabled = createExportState(tableMeta, ["id", "name"], matrix, [1, "Ada"], undefined, matrix.rows, [], DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, false, undefined, false, undefined, undefined, "dameng");
+ await disabled.copyWithExtractor("sql-inserts");
+ expect(vi.mocked(extractDataGridSelection).mock.lastCall?.[0].tableMeta?.schema).toBeUndefined();
+ });
+
it("does not report success when an extractor export save is cancelled", async () => {
const matrix: CellSelectionMatrix = { rowIndexes: [0], columnIndexes: [1], columns: ["name"], rows: [["Ada"]] };
vi.mocked(extractDataGridSelection).mockResolvedValueOnce({ text: '[{"name":"Ada"}]', mimeType: "application/json", fileExtension: "json", rowCount: 1, columnCount: 1 });
diff --git a/apps/desktop/src/composables/__tests__/useDataGridExtractor.includeDatabaseName.spec.ts b/apps/desktop/src/composables/__tests__/useDataGridExtractor.includeDatabaseName.spec.ts
new file mode 100644
index 0000000000..b984e25219
--- /dev/null
+++ b/apps/desktop/src/composables/__tests__/useDataGridExtractor.includeDatabaseName.spec.ts
@@ -0,0 +1,89 @@
+import { computed, ref } from "vue";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { useDataGridExtractor } from "@/composables/useDataGridExtractor";
+import { DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, type DataGridExtractorOptions } from "@/lib/dataGrid/dataGridCopyExtractor";
+import type { DataGridTableMeta } from "@/lib/dataGrid/dataGridSql";
+import type { DatabaseType } from "@/types/database";
+
+const mocks = vi.hoisted(() => ({ extract: vi.fn() }));
+vi.mock("@/lib/backend/api", () => ({ extractDataGridSelection: mocks.extract }));
+vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: vi.fn() }) }));
+vi.mock("vue-i18n", () => ({ useI18n: () => ({ t: (key: string) => key }) }));
+
+function createState(databaseType: DatabaseType, tableMeta: DataGridTableMeta, extractorOptions?: DataGridExtractorOptions, includeDatabaseName = false) {
+ const items = [{ id: 1, data: [1, "Ada"], isDraft: false }];
+ return useDataGridExtractor({
+ columns: computed(() => ["id", "name"]),
+ allColumns: computed(() => ["id", "name"]),
+ displayItems: computed(() => items),
+ allDisplayItems: computed(() => items),
+ allSourceColumns: computed(() => undefined),
+ visibleColumnIndexes: computed(() => [0, 1]),
+ columnTypes: computed(() => undefined),
+ extractorOptions: computed(() => extractorOptions ?? DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS),
+ databaseType: computed(() => databaseType),
+ identifierQuote: computed(() => undefined),
+ tableMeta: computed(() => tableMeta),
+ includeDatabaseName: computed(() => includeDatabaseName),
+ hasCellSelection: computed(() => false),
+ selectedCells: computed(() => ({ columns: [], rows: [] })),
+ selectedCellMatrix: computed(() => null),
+ hasRowSelection: computed(() => true),
+ hasColumnSelection: computed(() => false),
+ selectedRowIds: ref(new Set([1])),
+ contextCell: ref(null),
+ contextSelectionIsSynthetic: ref(false),
+ copyText: vi.fn(async () => true),
+ canCopySqlInsert: () => true,
+ buildMongoInsert: vi.fn(async () => undefined),
+ });
+}
+
+function qualifiedMeta(schema?: string, database?: string): DataGridTableMeta {
+ return {
+ tableName: "users",
+ schema,
+ database,
+ primaryKeys: ["id"],
+ columns: [
+ { name: "id", data_type: "int" },
+ { name: "name", data_type: "varchar" },
+ ],
+ };
+}
+
+async function capturedRequest(databaseType: DatabaseType, tableMeta: DataGridTableMeta, extractorOptions?: DataGridExtractorOptions, includeDatabaseName = false) {
+ const state = createState(databaseType, tableMeta, extractorOptions, includeDatabaseName);
+ const extraction = await state.extractWithExtractor("sql-inserts");
+ expect(extraction).not.toBeNull();
+ return mocks.extract.mock.calls[mocks.extract.mock.calls.length - 1]![0]!;
+}
+
+describe("useDataGridExtractor includeDatabaseName payload", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mocks.extract.mockResolvedValue({ text: "INSERT INTO users VALUES (1, 'Ada');", rowCount: 1 });
+ });
+
+ it.each([
+ ["sqlserver", "dbo", "app"],
+ ["snowflake", "PUBLIC", undefined],
+ ] as const)("keeps the namespace flag true for %s even when the option is off", async (databaseType, schema, database) => {
+ const request = await capturedRequest(databaseType, qualifiedMeta(schema, database));
+ expect(request.tableMeta.schema).toBe(schema);
+ expect(request.options.sql.includeDatabaseName).toBe(true);
+ });
+
+ it("strips optional qualifiers and keeps the flag off for non schema-qualified dialects", async () => {
+ const request = await capturedRequest("mysql", qualifiedMeta(undefined, "mydb"));
+ expect(request.tableMeta.database).toBeUndefined();
+ expect(request.tableMeta.schema).toBeUndefined();
+ expect(request.options.sql.includeDatabaseName).toBe(false);
+ });
+
+ it("preserves an explicit opt-in for non schema-qualified dialects", async () => {
+ const request = await capturedRequest("mysql", qualifiedMeta(undefined, "mydb"), { ...DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, sql: { ...DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS.sql, includeDatabaseName: true } });
+ expect(request.tableMeta.database).toBe("mydb");
+ expect(request.options.sql.includeDatabaseName).toBe(true);
+ });
+});
diff --git a/apps/desktop/src/composables/useDataGridExtractor.ts b/apps/desktop/src/composables/useDataGridExtractor.ts
index ad5b0e3dc9..fbd446617f 100644
--- a/apps/desktop/src/composables/useDataGridExtractor.ts
+++ b/apps/desktop/src/composables/useDataGridExtractor.ts
@@ -230,6 +230,10 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
const presentBinaryText = descriptor.category !== "sql";
const rawRows = sourceRows.map((row) => requiredSourceIndexes.map((sourceIndex) => row[sourceIndex]));
const rows = rawRows.map((row) => row.map((value, index) => extractorCellValue(value, columnTypes[index], normalizeValues, keepUnsafeJsonText, presentBinaryText, index)));
+ // 「包含数据库名称」:复制/导出 SQL 时,提取器自身的勾选框与编辑器全局设置
+ // (`generateSqlIncludeDatabaseName`)任一开启都保留库名/模式名;后者还会影响
+ // 保存 SQL、侧栏导出等其它路径,因此这里取“或”,不覆盖全局偏好。
+ const extractorOptionValues = normalizeDataGridExtractorOptions(extractorOptions);
const tableMeta =
descriptor.category === "sql"
? tableMetaWithoutOptionalDatabaseQualifier(
@@ -238,7 +242,7 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
columns.map((column) => column.sourceName ?? column.displayName),
),
options.databaseType.value,
- options.includeDatabaseName?.value,
+ options.includeDatabaseName?.value === true || extractorOptionValues.sql.includeDatabaseName === true,
)
: undefined;
const request: DataGridExtractRequest = {
@@ -251,7 +255,12 @@ export function useDataGridExtractor(options: UseDataGridExtractorOptions) {
selectedColumnIndexes,
rows,
selectionKind,
- options: normalizeDataGridExtractorOptions(extractorOptions),
+ // For dialects whose tableMeta keeps the schema qualifier regardless of
+ // the flag (DATABASE_SCHEMA_QUALIFIED_TYPES), the backend strips every
+ // namespace prefix when include_database_name=false, which would regress
+ // the default output to a bare table name; keep the flag aligned with
+ // the qualified tableMeta we intentionally preserved.
+ options: tableMeta && (tableMeta.schema !== undefined || tableMeta.database !== undefined) ? { ...extractorOptionValues, sql: { ...extractorOptionValues.sql, includeDatabaseName: true } } : extractorOptionValues,
};
requestSources.set(request, {
rowIds: sourceRowIds,
diff --git a/apps/desktop/src/lib/__tests__/dataGrid/dataGridCopyExtractor.spec.ts b/apps/desktop/src/lib/__tests__/dataGrid/dataGridCopyExtractor.spec.ts
index 6b587a7543..5fef626940 100644
--- a/apps/desktop/src/lib/__tests__/dataGrid/dataGridCopyExtractor.spec.ts
+++ b/apps/desktop/src/lib/__tests__/dataGrid/dataGridCopyExtractor.spec.ts
@@ -12,11 +12,13 @@ describe("data-grid extractor options", () => {
expect(normalizeDataGridExtractorOptions({ sql: { quoteIdentifiers: "false", temporalFormat: "custom" } }).sql).toMatchObject({ quoteIdentifiers: true, temporalFormat: "native" });
});
- it("keeps database qualification for legacy options and persists explicit opt-out", () => {
- expect(normalizeDataGridExtractorOptions({ sql: {} }).sql.includeDatabaseName).toBe(true);
- const configured = normalizeDataGridExtractorOptions({ sql: { includeDatabaseName: false } });
- expect(normalizeDataGridExtractorOptions(JSON.parse(JSON.stringify(configured))).sql.includeDatabaseName).toBe(false);
- expect(normalizeDataGridExtractorOptions({ sql: { includeDatabaseName: "false" } }).sql.includeDatabaseName).toBe(true);
+ it("defaults to dropping database qualification and keeps an explicit opt-in", () => {
+ expect(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS.sql.includeDatabaseName).toBe(false);
+ expect(normalizeDataGridExtractorOptions({ sql: {} }).sql.includeDatabaseName).toBe(false);
+ const configured = normalizeDataGridExtractorOptions({ sql: { includeDatabaseName: true } });
+ expect(configured.sql.includeDatabaseName).toBe(true);
+ expect(normalizeDataGridExtractorOptions(JSON.parse(JSON.stringify(configured))).sql.includeDatabaseName).toBe(true);
+ expect(normalizeDataGridExtractorOptions({ sql: { includeDatabaseName: "true" } }).sql.includeDatabaseName).toBe(false);
});
it("defaults DSV NULL output to an empty spreadsheet field", () => {
diff --git a/apps/desktop/src/lib/__tests__/table/tableSelectSql.spec.ts b/apps/desktop/src/lib/__tests__/table/tableSelectSql.spec.ts
index 1239c7f587..307206adde 100644
--- a/apps/desktop/src/lib/__tests__/table/tableSelectSql.spec.ts
+++ b/apps/desktop/src/lib/__tests__/table/tableSelectSql.spec.ts
@@ -239,6 +239,20 @@ describe("tableMetaWithoutOptionalDatabaseQualifier — copy extractors (#9326)"
expect(tableMetaWithoutOptionalDatabaseQualifier({ schema: "public", tableName: "users" }, "postgres", false)).toEqual({ schema: undefined, tableName: "users" });
});
+ it("covers the copy-SQL dialects reported in #11195", () => {
+ // Dameng exposes schemas as databases; a copied INSERT/UPDATE must still be
+ // able to opt into the schema prefix, so the qualifier only drops when the
+ // extractor option is off.
+ const damengMeta = { schema: "SYSDBA", tableName: "PEOPLE" };
+ expect(tableMetaWithoutOptionalDatabaseQualifier(damengMeta, "dameng", false)).toEqual({ schema: undefined, tableName: "PEOPLE" });
+ expect(tableMetaWithoutOptionalDatabaseQualifier(damengMeta, "dameng", true)).toBe(damengMeta);
+ // MySQL family and ClickHouse address tables as database.table.
+ const clickhouseMeta = { database: "analytics", tableName: "events" };
+ expect(tableMetaWithoutOptionalDatabaseQualifier(clickhouseMeta, "clickhouse", false)).toEqual({ database: undefined, tableName: "events" });
+ expect(tableMetaWithoutOptionalDatabaseQualifier(clickhouseMeta, "clickhouse", true)).toBe(clickhouseMeta);
+ expect(tableMetaWithoutOptionalDatabaseQualifier({ database: "appdb", tableName: "people" }, "goldendb", false)).toEqual({ database: undefined, tableName: "people" });
+ });
+
it("returns undefined tableMeta unchanged", () => {
expect(tableMetaWithoutOptionalDatabaseQualifier(undefined, "mysql", false)).toBeUndefined();
});
diff --git a/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts b/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts
index c05221a59d..ddeabbb54a 100644
--- a/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts
+++ b/apps/desktop/src/lib/dataGrid/dataGridCopyExtractor.ts
@@ -29,7 +29,9 @@ export const DATA_GRID_DEFAULT_COPY_PREFERENCES: readonly DataGridCopyPreference
];
export const DATA_GRID_EXTRACTOR_CONTRACT_VERSION = 1 as const;
-export const DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION = 1 as const;
+// v2:复制/导出 SQL 的「包含数据库名称」改为由提取器自身的勾选框决定,默认关闭(不带库名/模式名),
+// 历史持久化的 `true` 由 settingsStore 的迁移归位到新默认。
+export const DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION = 2 as const;
export const DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS: Record = {
raw: { category: "raw" },
@@ -151,7 +153,7 @@ export const DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS: DataGridExtractorOptions = {
skipGeneratedColumns: true,
insertMode: "merged",
excludePrimaryKeysFromInsert: false,
- includeDatabaseName: true,
+ includeDatabaseName: false,
quoteIdentifiers: true,
temporalFormat: "native",
},
@@ -184,7 +186,7 @@ export function normalizeDataGridExtractorOptions(value: unknown): DataGridExtra
skipGeneratedColumns: sql.skipGeneratedColumns !== false,
insertMode: sql.insertMode === "row-by-row" ? "row-by-row" : "merged",
excludePrimaryKeysFromInsert: sql.excludePrimaryKeysFromInsert === true,
- includeDatabaseName: sql.includeDatabaseName !== false,
+ includeDatabaseName: sql.includeDatabaseName === true,
quoteIdentifiers: sql.quoteIdentifiers !== false,
temporalFormat: sql.temporalFormat === "string" ? "string" : "native",
},
diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
index a81b7ccb67..5dad4f0a31 100644
--- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
+++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts
@@ -611,6 +611,23 @@ describe("normalizeEditorSettings", () => {
expect(configured.dataGridExtractorOptions.dsv.nullText).toBe("NULL");
});
+ it("resets the copy extractor database-name opt-in once when migrating to v2", () => {
+ const legacy = normalizeEditorSettings({
+ dataGridExtractorOptionsMigrationVersion: 1,
+ dataGridExtractorOptions: { sql: { includeDatabaseName: true } },
+ });
+ // 历史持久化的「包含数据库名称」归位到新默认:复制/导出 SQL 默认不带库名/模式名
+ expect(legacy.dataGridExtractorOptions.sql.includeDatabaseName).toBe(false);
+ expect(legacy.dataGridExtractorOptionsMigrationVersion).toBe(DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION);
+
+ const current = normalizeEditorSettings({
+ dataGridExtractorOptionsMigrationVersion: DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION,
+ dataGridExtractorOptions: { sql: { includeDatabaseName: true } },
+ });
+ // 迁移之后用户的显式勾选必须被保留
+ expect(current.dataGridExtractorOptions.sql.includeDatabaseName).toBe(true);
+ });
+
it("defaults retained result runs to tiled tabs and preserves list mode", () => {
expect(normalizeEditorSettings({}).resultRunDisplayMode).toBe("tabs");
expect(normalizeEditorSettings({ resultRunDisplayMode: "list" }).resultRunDisplayMode).toBe("list");
diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts
index 0ebe090294..2ed92aaa88 100644
--- a/apps/desktop/src/stores/settingsStore.ts
+++ b/apps/desktop/src/stores/settingsStore.ts
@@ -1783,7 +1783,14 @@ export function normalizeEditorSettings(settings: Partial, exist
const savedExtractorMigrationVersion = settings.dataGridExtractorOptionsMigrationVersion;
const normalizedExtractorOptions = normalizeDataGridExtractorOptions(settings.dataGridExtractorOptions);
const isLegacyExtractorOptions = typeof savedExtractorMigrationVersion !== "number" || savedExtractorMigrationVersion < DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION;
- const dataGridExtractorOptions = isLegacyExtractorOptions && normalizedExtractorOptions.dsv.nullText === "NULL" ? { ...normalizedExtractorOptions, dsv: { ...normalizedExtractorOptions.dsv, nullText: "" } } : normalizedExtractorOptions;
+ const dataGridExtractorOptions = (() => {
+ if (!isLegacyExtractorOptions) return normalizedExtractorOptions;
+ // v1:旧的 "NULL" 文本迁移为空串
+ const nullTextMigrated = normalizedExtractorOptions.dsv.nullText === "NULL" ? { ...normalizedExtractorOptions, dsv: { ...normalizedExtractorOptions.dsv, nullText: "" } } : normalizedExtractorOptions;
+ // v2:复制/导出 SQL 的「包含数据库名称」改为由提取器勾选框决定且默认关闭,
+ // 历史持久化的 true 一并归位到新默认(想带库名/模式名的用户重新勾选即可)
+ return nullTextMigrated.sql.includeDatabaseName ? { ...nullTextMigrated, sql: { ...nullTextMigrated.sql, includeDatabaseName: false } } : nullTextMigrated;
+ })();
// Preserve the explicit intent behind the legacy update controls. Disabling
// update reminders was a full opt-out; disabling automatic downloads only
// opted out of downloading the DBX package itself.
@@ -2302,7 +2309,12 @@ export const useSettingsStore = defineStore("settings", () => {
const needsWelcomePageDefaultMigration = typeof savedSettings.welcomePageModeDefaultVersion !== "number" || savedSettings.welcomePageModeDefaultVersion < WELCOME_PAGE_DEFAULT_VERSION;
const needsTabNavigationShortcutMigration = needsTabNavigationHistoryShortcutMigration(savedSettings.shortcuts);
const savedNullText = (savedSettings.dataGridExtractorOptions as Partial | undefined)?.dsv?.nullText;
- const needsDataGridExtractorOptionsMigration = (typeof savedSettings.dataGridExtractorOptionsMigrationVersion !== "number" || savedSettings.dataGridExtractorOptionsMigrationVersion < DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION) && savedNullText === "NULL";
+ // v2 also resets a historically persisted includeDatabaseName=true, so
+ // trigger the eager persist for that stale disk value even when the
+ // v1 "NULL" marker is already migrated away.
+ const savedSqlIncludeDatabaseName = (savedSettings.dataGridExtractorOptions as Partial | undefined)?.sql?.includeDatabaseName;
+ const needsDataGridExtractorOptionsMigration =
+ (typeof savedSettings.dataGridExtractorOptionsMigrationVersion !== "number" || savedSettings.dataGridExtractorOptionsMigrationVersion < DATA_GRID_EXTRACTOR_OPTIONS_MIGRATION_VERSION) && (savedNullText === "NULL" || savedSqlIncludeDatabaseName === true);
const savedUpdateDownloadSource = (saved as { updateDownloadSource?: unknown }).updateDownloadSource;
if (savedUpdateDownloadSource === "atomgit" || needsExecuteModeDefaultMigration || needsWelcomePageDefaultMigration || needsTabNavigationShortcutMigration || needsSidebarBrowseObjectsMigration || needsDataGridExtractorOptionsMigration) {
// Persist one-time migrations so removed or unsafe defaults cannot reappear.