diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 894ab3bbe6..f2137045de 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -202,7 +202,7 @@ import { savedSqlClipboardFileIds, savedSqlPasteTargetForNode } from "@/lib/save import { exportSavedSqlFileContent } from "@/lib/savedSql/savedSqlExport"; import { isSqlServerLinkedNode } from "@/lib/database/sqlServerLinkedServers"; import { flattenTree } from "@/composables/useFlatTree"; -import { createDatabaseCollationOptionsForCharset, DEFAULT_GBASE8S_DATABASE_LOCALE, GBASE8S_DATABASE_LOCALES, nextCreateDatabaseCollation, normalizeCreateDatabaseCharset, parseCreateDatabaseCharsetMetadata } from "@/lib/database/createDatabaseCharsetOptions"; +import { createDatabaseCollationOptionsForCharset, DEFAULT_GBASE8S_DATABASE_LOCALE, defaultGbase8sDatabaseLocale, GBASE8S_DATABASE_LOCALES, nextCreateDatabaseCollation, normalizeCreateDatabaseCharset, parseCreateDatabaseCharsetMetadata } from "@/lib/database/createDatabaseCharsetOptions"; import { executeWithProductionContextGuard, executeWithProductionSqlGuard } from "@/lib/database/productionExecutionGuard"; import { connectionIsEffectivelyReadOnly } from "@/lib/database/readOnlyWriteAccess"; import { buildXuguCompileSql } from "@/lib/database/xuguCompileSql"; @@ -3828,12 +3828,13 @@ function openCreateDatabaseDialog() { showCreateDatabaseDialog.value = true; if (canSetCreateDatabaseLocale.value) { // GBase 8s / Informix: the "charset" control selects the new database's DB_LOCALE, which the - // agent applies by opening the CREATE DATABASE session with it. Default to UTF-8 so new - // databases can store Chinese; no server charset catalog query is needed. + // agent applies by opening the CREATE DATABASE session with it. Seed a UTF-8 default, then + // repopulate the options with the collations already present on the connected instance. createDatabaseCharsetOptions.value = [...GBASE8S_DATABASE_LOCALES]; createDatabaseCollationsByCharset.value = {}; createDatabaseCharset.value = DEFAULT_GBASE8S_DATABASE_LOCALE; createDatabaseCollation.value = ""; + void loadGbase8sDatabaseLocales(); } else if (canSetCreateDatabaseCharset.value) { void loadCreateDatabaseCharsetMetadata(); } @@ -3958,6 +3959,33 @@ async function loadCreateDatabaseCharsetMetadata(target: "create" | "edit" = "cr } } +async function loadGbase8sDatabaseLocales() { + const node = activeNode.value; + if (!node.connectionId) return; + createDatabaseCharsetLoading.value = true; + try { + await connectionStore.ensureConnected(node.connectionId); + const result = await api.executeQuery(node.connectionId, "", "SELECT DISTINCT dbs_collate FROM sysmaster:sysdbslocale ORDER BY 1", undefined, undefined, { maxRows: 500 }); + if (!showCreateDatabaseDialog.value) return; + const idx = result.columns.findIndex((column) => column.trim().toLowerCase() === "dbs_collate"); + const locales = [...new Set(result.rows.map((row) => String(row[idx >= 0 ? idx : 0] ?? "").trim()).filter(Boolean))]; + if (!locales.length) throw new Error("no collations"); + createDatabaseCharsetOptions.value = locales; + createDatabaseCollationsByCharset.value = {}; + if (!locales.includes(createDatabaseCharset.value)) { + updateCreateDatabaseCharset(defaultGbase8sDatabaseLocale(locales)); + } + } catch { + createDatabaseCharsetOptions.value = [...GBASE8S_DATABASE_LOCALES]; + createDatabaseCollationsByCharset.value = {}; + if (!createDatabaseCharsetOptions.value.includes(createDatabaseCharset.value)) { + updateCreateDatabaseCharset(DEFAULT_GBASE8S_DATABASE_LOCALE); + } + } finally { + createDatabaseCharsetLoading.value = false; + } +} + function ensureDuckDbFileExtension(path: string): string { return /\.(duckdb|db)$/i.test(path) ? path : `${path}.duckdb`; } diff --git a/apps/desktop/src/lib/__tests__/database/createDatabaseLocale.spec.ts b/apps/desktop/src/lib/__tests__/database/createDatabaseLocale.spec.ts index 4c4c11fa09..6bea85ccbe 100644 --- a/apps/desktop/src/lib/__tests__/database/createDatabaseLocale.spec.ts +++ b/apps/desktop/src/lib/__tests__/database/createDatabaseLocale.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { supportsCreateDatabaseCharset, supportsCreateDatabaseLocale } from "@/lib/database/createDatabaseSql"; -import { DEFAULT_GBASE8S_DATABASE_LOCALE, GBASE8S_DATABASE_LOCALES } from "@/lib/database/createDatabaseCharsetOptions"; +import { DEFAULT_GBASE8S_DATABASE_LOCALE, defaultGbase8sDatabaseLocale, GBASE8S_DATABASE_LOCALES } from "@/lib/database/createDatabaseCharsetOptions"; describe("GBase 8s / Informix create-database locale support", () => { it("offers a locale picker only for the gbase8s profile", () => { @@ -22,4 +22,16 @@ describe("GBase 8s / Informix create-database locale support", () => { expect(GBASE8S_DATABASE_LOCALES).toContain("zh_CN.utf8"); expect(DEFAULT_GBASE8S_DATABASE_LOCALE).toBe("zh_CN.utf8"); }); + + it("seeds the default from instance locales reporting the numeric UTF-8 code set", () => { + // Real GBase 8s instances report dbs_collate like zh_CN.57372 (57372 = UTF-8) alongside + // en_US.819 (ISO-8859-1); after ORDER BY 1 the plain first entry cannot hold CJK. + expect(defaultGbase8sDatabaseLocale(["en_US.819", "zh_CN.57372"])).toBe("zh_CN.57372"); + expect(defaultGbase8sDatabaseLocale(["en_US.819", "zh_CN.utf8"])).toBe("zh_CN.utf8"); + }); + + it("falls back to the first locale or the static default when no UTF-8 option exists", () => { + expect(defaultGbase8sDatabaseLocale(["en_US.819"])).toBe("en_US.819"); + expect(defaultGbase8sDatabaseLocale([])).toBe(DEFAULT_GBASE8S_DATABASE_LOCALE); + }); }); diff --git a/apps/desktop/src/lib/database/createDatabaseCharsetOptions.ts b/apps/desktop/src/lib/database/createDatabaseCharsetOptions.ts index eb92dc429c..371492eec6 100644 --- a/apps/desktop/src/lib/database/createDatabaseCharsetOptions.ts +++ b/apps/desktop/src/lib/database/createDatabaseCharsetOptions.ts @@ -16,6 +16,12 @@ export const CREATE_DATABASE_CHARSET_OPTIONS = ["utf8mb4", "utf8", "gbk", "latin export const GBASE8S_DATABASE_LOCALES = ["zh_CN.utf8", "en_US.utf8", "ja_JP.utf8", "ko_KR.utf8", "zh_CN.gbk", "zh_CN.gb18030", "en_US.819"] as const; export const DEFAULT_GBASE8S_DATABASE_LOCALE = "zh_CN.utf8"; +// Informix-family instances report dbs_collate with the numeric code set (57372 = UTF-8, +// 819 = ISO-8859-1) instead of the "utf8" mnemonic, so both spellings must seed the default. +export function defaultGbase8sDatabaseLocale(locales: string[]): string { + return locales.find((locale) => /(?:utf8|57372)/i.test(locale)) ?? locales[0] ?? DEFAULT_GBASE8S_DATABASE_LOCALE; +} + const FALLBACK_COLLATION_OPTIONS: Record = { ascii: ["ascii_general_ci", "ascii_bin"], big5: ["big5_chinese_ci", "big5_bin"],