feat(gbase8s): split GBase 8s into its own dbType

Closes #12486
This commit is contained in:
t8y2
2026-10-16 21:33:43 +08:00
parent 854077dee8
commit a7d6b80e2c
50 changed files with 245 additions and 139 deletions
@@ -9241,7 +9241,7 @@ function openExternalUrl(url: string) {
<Input v-model="elasticsearchIndexGroupingPattern" class="col-span-3" :placeholder="t('connection.elasticsearchIndexGroupingPatternPlaceholder')" @input="resetTestState" /> <Input v-model="elasticsearchIndexGroupingPattern" class="col-span-3" :placeholder="t('connection.elasticsearchIndexGroupingPatternPlaceholder')" @input="resetTestState" />
</div> </div>
<div v-if="form.driver_profile === 'gbase8s'" class="grid grid-cols-4 items-center gap-4"> <div v-if="form.db_type === 'gbase8s'" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.gbaseServer") }}</Label> <Label :class="connectionLabelSmallClass">{{ t("connection.gbaseServer") }}</Label>
<div class="col-span-3 space-y-1"> <div class="col-span-3 space-y-1">
<Input v-model="form.gbase_server" placeholder="gbase01" /> <Input v-model="form.gbase_server" placeholder="gbase01" />
@@ -4440,7 +4440,7 @@ const canSetCreateDatabaseCharset = computed(() => {
const canSetCreateDatabaseLocale = computed(() => { const canSetCreateDatabaseLocale = computed(() => {
const config = activeNode.value.connectionId ? connectionStore.getConfig(activeNode.value.connectionId) : undefined; const config = activeNode.value.connectionId ? connectionStore.getConfig(activeNode.value.connectionId) : undefined;
return connectionNamespaceCreationTarget(config) === "database" && supportsCreateDatabaseLocale(config?.db_type, config?.driver_profile); return connectionNamespaceCreationTarget(config) === "database" && supportsCreateDatabaseLocale(config?.db_type);
}); });
const canDropDatabase = computed(() => { const canDropDatabase = computed(() => {
@@ -124,7 +124,7 @@ describe("useSidebarTableMutationRuntime SAP HANA schema-scoped actions", () =>
}); });
it("omits the GBase 8s metadata owner from generated table SQL", async () => { it("omits the GBase 8s metadata owner from generated table SQL", async () => {
const { feature } = runtime("tenant", { db_type: "gbase", driver_profile: "gbase8s" }, "informix"); const { feature } = runtime("tenant", { db_type: "gbase8s", driver_profile: "gbase8s" }, "informix");
await feature.confirmDropTable(); await feature.confirmDropTable();
@@ -302,7 +302,7 @@ describe("buildConnectionUrlCopy JDBC URL", () => {
it("selects dialects by driver profile", () => { it("selects dialects by driver profile", () => {
expect(buildConnectionUrlCopy(config({ db_type: "gaussdb", driver_profile: "gaussdb-m" }), "jdbcUrl")).toBe("jdbc:gaussdb://db.example.com:5432/appdb"); expect(buildConnectionUrlCopy(config({ db_type: "gaussdb", driver_profile: "gaussdb-m" }), "jdbcUrl")).toBe("jdbc:gaussdb://db.example.com:5432/appdb");
expect(buildConnectionUrlCopy(config({ db_type: "gaussdb" }), "jdbcUrl")).toBe("jdbc:postgresql://db.example.com:5432/appdb"); expect(buildConnectionUrlCopy(config({ db_type: "gaussdb" }), "jdbcUrl")).toBe("jdbc:postgresql://db.example.com:5432/appdb");
expect(buildConnectionUrlCopy(config({ db_type: "gbase", port: 9088, driver_profile: "gbase8s" }), "jdbcUrl")).toBe("jdbc:gbasedbt-sqli://db.example.com:9088/appdb"); expect(buildConnectionUrlCopy(config({ db_type: "gbase8s", port: 9088, driver_profile: "gbase8s" }), "jdbcUrl")).toBe("jdbc:gbasedbt-sqli://db.example.com:9088/appdb");
expect(buildConnectionUrlCopy(config({ db_type: "gbase", port: 5258, driver_profile: "gbase8a" }), "jdbcUrl")).toBeNull(); expect(buildConnectionUrlCopy(config({ db_type: "gbase", port: 5258, driver_profile: "gbase8a" }), "jdbcUrl")).toBeNull();
expect(buildConnectionUrlCopy(config({ db_type: "tdengine", port: 6041 }), "jdbcUrl")).toBe("jdbc:TAOS-RS://db.example.com:6041/appdb"); expect(buildConnectionUrlCopy(config({ db_type: "tdengine", port: 6041 }), "jdbcUrl")).toBe("jdbc:TAOS-RS://db.example.com:6041/appdb");
expect(buildConnectionUrlCopy(config({ db_type: "tdengine", port: 6030 }), "jdbcUrl")).toBe("jdbc:TAOS://db.example.com:6030/appdb"); expect(buildConnectionUrlCopy(config({ db_type: "tdengine", port: 6030 }), "jdbcUrl")).toBe("jdbc:TAOS://db.example.com:6030/appdb");
@@ -28,7 +28,7 @@ function selectedProfileHarness(selectedTypeValue: string, driverProfile: string
gbase8c: { label: "南大通用 GBase 8c" }, gbase8c: { label: "南大通用 GBase 8c" },
}, },
{ value: selectedTypeValue }, { value: selectedTypeValue },
{ value: { db_type: driverProfile === "gbase8c" ? "gbase8c" : "gbase", driver_profile: driverProfile } }, { value: { db_type: driverProfile === "gbase8c" ? "gbase8c" : driverProfile === "gbase8s" ? "gbase8s" : "gbase", driver_profile: driverProfile } },
() => null, () => null,
{ value: null }, { value: null },
gbaseProfileFromConfig, gbaseProfileFromConfig,
@@ -11,6 +11,9 @@ describe("gbaseProfiles", () => {
it("resolves the toggle profile from a saved connection", () => { it("resolves the toggle profile from a saved connection", () => {
expect(gbaseProfileFromConfig({ db_type: "gbase8c", driver_profile: "gbase8c" })).toBe("gbase8c"); expect(gbaseProfileFromConfig({ db_type: "gbase8c", driver_profile: "gbase8c" })).toBe("gbase8c");
expect(gbaseProfileFromConfig({ db_type: "gbase8c" })).toBe("gbase8c"); expect(gbaseProfileFromConfig({ db_type: "gbase8c" })).toBe("gbase8c");
expect(gbaseProfileFromConfig({ db_type: "gbase8s", driver_profile: "gbase8s" })).toBe("gbase8s");
expect(gbaseProfileFromConfig({ db_type: "gbase8s" })).toBe("gbase8s");
// A not-yet-migrated in-memory config still resolves to the 8s toggle.
expect(gbaseProfileFromConfig({ db_type: "gbase", driver_profile: "gbase8s" })).toBe("gbase8s"); expect(gbaseProfileFromConfig({ db_type: "gbase", driver_profile: "gbase8s" })).toBe("gbase8s");
expect(gbaseProfileFromConfig({ db_type: "gbase", driver_profile: "gbase8a" })).toBe("gbase8a"); expect(gbaseProfileFromConfig({ db_type: "gbase", driver_profile: "gbase8a" })).toBe("gbase8a");
expect(gbaseProfileFromConfig({ db_type: "gbase", driver_profile: "gbase" })).toBe("gbase8a"); expect(gbaseProfileFromConfig({ db_type: "gbase", driver_profile: "gbase" })).toBe("gbase8a");
@@ -3,19 +3,19 @@ import { supportsCreateDatabaseCharset, supportsCreateDatabaseLocale } from "@/l
import { DEFAULT_GBASE8S_DATABASE_LOCALE, defaultGbase8sDatabaseLocale, 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", () => { describe("GBase 8s / Informix create-database locale support", () => {
it("offers a locale picker only for the gbase8s profile", () => { it("offers a locale picker only for the gbase8s type", () => {
expect(supportsCreateDatabaseLocale("gbase", "gbase8s")).toBe(true); expect(supportsCreateDatabaseLocale("gbase8s")).toBe(true);
// Plain Informix's agent has no directive handling, so the locale picker must stay hidden // Plain Informix's agent has no directive handling, so the locale picker must stay hidden
// there until InformixAgent implements it; showing it would silently ignore the choice. // there until InformixAgent implements it; showing it would silently ignore the choice.
expect(supportsCreateDatabaseLocale("informix", undefined)).toBe(false); expect(supportsCreateDatabaseLocale("informix")).toBe(false);
}); });
it("does not treat GBase 8a or MySQL as locale-picking", () => { it("does not treat GBase 8a or MySQL as locale-picking", () => {
expect(supportsCreateDatabaseLocale("gbase", "gbase8a")).toBe(false); expect(supportsCreateDatabaseLocale("gbase")).toBe(false);
expect(supportsCreateDatabaseLocale("mysql", "mysql")).toBe(false); expect(supportsCreateDatabaseLocale("mysql")).toBe(false);
// The MySQL charset path stays independent of the Informix locale path. // The MySQL charset path stays independent of the Informix locale path.
expect(supportsCreateDatabaseCharset("mysql", "mysql")).toBe(true); expect(supportsCreateDatabaseCharset("mysql", "mysql")).toBe(true);
expect(supportsCreateDatabaseCharset("gbase", "gbase8s")).toBe(false); expect(supportsCreateDatabaseCharset("gbase8s", "gbase8s")).toBe(false);
}); });
it("defaults to a Chinese-capable UTF-8 locale", () => { it("defaults to a Chinese-capable UTF-8 locale", () => {
@@ -7,7 +7,7 @@ const databaseNode = { type: "database" as const, database: "dcss" };
describe("database namespace creation targets", () => { describe("database namespace creation targets", () => {
it("offers Create Database (not Create Schema) for GBase 8s", () => { it("offers Create Database (not Create Schema) for GBase 8s", () => {
const gbase8s = { db_type: "gbase", driver_profile: "gbase8s" } as Conn; const gbase8s = { db_type: "gbase8s", driver_profile: "gbase8s" } as Conn;
expect(connectionNamespaceCreationTarget(gbase8s)).toBe("database"); expect(connectionNamespaceCreationTarget(gbase8s)).toBe("database");
expect(databaseNodeNamespaceCreationTarget(gbase8s, databaseNode)).toBeNull(); expect(databaseNodeNamespaceCreationTarget(gbase8s, databaseNode)).toBeNull();
@@ -159,7 +159,7 @@ describe("jdbc dialect inference", () => {
expect(connectionShouldLoadIdentifierQuote({ db_type: "kingbase" })).toBe(true); expect(connectionShouldLoadIdentifierQuote({ db_type: "kingbase" })).toBe(true);
expect(connectionShouldLoadIdentifierQuote({ db_type: "kyuubi" })).toBe(true); expect(connectionShouldLoadIdentifierQuote({ db_type: "kyuubi" })).toBe(true);
expect(connectionShouldLoadIdentifierQuote({ db_type: "gaussdb" })).toBe(true); expect(connectionShouldLoadIdentifierQuote({ db_type: "gaussdb" })).toBe(true);
expect(connectionShouldLoadIdentifierQuote({ db_type: "gbase", driver_profile: "gbase8s" })).toBe(true); expect(connectionShouldLoadIdentifierQuote({ db_type: "gbase8s", driver_profile: "gbase8s" })).toBe(true);
expect(connectionShouldLoadIdentifierQuote({ db_type: "gbase", driver_profile: "gbase8a" })).toBe(false); expect(connectionShouldLoadIdentifierQuote({ db_type: "gbase", driver_profile: "gbase8a" })).toBe(false);
// Cloud Spanner reports a backtick for GoogleSQL and a double quote for the // Cloud Spanner reports a backtick for GoogleSQL and a double quote for the
// PostgreSQL dialect; both are only known from the connection. // PostgreSQL dialect; both are only known from the connection.
@@ -175,12 +175,12 @@ describe("jdbc dialect inference", () => {
}); });
it("falls back to a flat table tree when GBase 8s reports no schemas", () => { it("falls back to a flat table tree when GBase 8s reports no schemas", () => {
expect(connectionShouldDiscoverJdbcSchemas({ db_type: "gbase", driver_profile: "gbase8s" })).toBe(true); expect(connectionShouldDiscoverJdbcSchemas({ db_type: "gbase8s", driver_profile: "gbase8s" })).toBe(true);
expect(connectionShouldDiscoverJdbcSchemas({ db_type: "gbase", driver_profile: "gbase8a" })).toBe(false); expect(connectionShouldDiscoverJdbcSchemas({ db_type: "gbase", driver_profile: "gbase8a" })).toBe(false);
}); });
it("omits the metadata owner from GBase 8s table SQL", () => { it("omits the metadata owner from GBase 8s table SQL", () => {
expect(connectionTableSqlSchema({ db_type: "gbase", driver_profile: "gbase8s" }, "gbasedbt")).toBeUndefined(); expect(connectionTableSqlSchema({ db_type: "gbase8s", driver_profile: "gbase8s" }, "gbasedbt")).toBeUndefined();
expect(connectionTableSqlSchema({ db_type: "gbase", driver_profile: "gbase8a" }, "analytics")).toBe("analytics"); expect(connectionTableSqlSchema({ db_type: "gbase", driver_profile: "gbase8a" }, "analytics")).toBe("analytics");
expect(connectionTableSqlSchema({ db_type: "informix", driver_profile: "informix" }, "informix")).toBe("informix"); expect(connectionTableSqlSchema({ db_type: "informix", driver_profile: "informix" }, "informix")).toBe("informix");
}); });
@@ -485,7 +485,7 @@ describe("object tree node schema", () => {
it.each([ it.each([
{ db_type: "jdbc" as const, connection_string: "jdbc:informix-sqli://localhost:9088/prulife" }, { db_type: "jdbc" as const, connection_string: "jdbc:informix-sqli://localhost:9088/prulife" },
{ db_type: "gbase" as const, driver_profile: "gbase8s" }, { db_type: "gbase8s" as const, driver_profile: "gbase8s" },
])("keeps compatible Informix metadata on the login owner", (connection) => { ])("keeps compatible Informix metadata on the login owner", (connection) => {
expect(connectionObjectTreeQuerySchema(connection, "prulife")).toBe(""); expect(connectionObjectTreeQuerySchema(connection, "prulife")).toBe("");
expect(connectionObjectTreeNodeSchema(connection, "prulife")).toBeUndefined(); expect(connectionObjectTreeNodeSchema(connection, "prulife")).toBeUndefined();
@@ -17,7 +17,7 @@ describe("sidebar database storage", () => {
} }
expect(supportsSidebarTableStorage(config("jdbc"))).toBe(false); expect(supportsSidebarTableStorage(config("jdbc"))).toBe(false);
expect(supportsSidebarTableStorage({ ...config("postgres"), driver_profile: "cockroachdb" })).toBe(false); expect(supportsSidebarTableStorage({ ...config("postgres"), driver_profile: "cockroachdb" })).toBe(false);
expect(supportsSidebarTableStorage({ ...config("gbase"), driver_profile: "gbase8s" })).toBe(false); expect(supportsSidebarTableStorage({ ...config("gbase8s"), driver_profile: "gbase8s" })).toBe(false);
}); });
it("requests and applies only visible database nodes", () => { it("requests and applies only visible database nodes", () => {
@@ -50,7 +50,7 @@ export function agentDriverInstallKey(dbType: DatabaseType | undefined, driverPr
} }
if (dbType === "mongodb") return "mongodb"; if (dbType === "mongodb") return "mongodb";
if (dbType === "dameng") return "dameng"; if (dbType === "dameng") return "dameng";
if (dbType === "gbase") return driverProfile === "gbase8s" ? "gbase8s" : "gbase8a"; if (dbType === "gbase") return "gbase8a";
if (dbType === "mq") { if (dbType === "mq") {
if (driverProfile === "kafka") return "kafka"; if (driverProfile === "kafka") return "kafka";
if (driverProfile === "rocketmq") return "rocketmq"; if (driverProfile === "rocketmq") return "rocketmq";
@@ -24,6 +24,7 @@ const DRIVER_STARTUP_FLOOR_TYPES = new Set<DatabaseType>([
"exasol", "exasol",
"oceanbase-oracle", "oceanbase-oracle",
"gbase", "gbase",
"gbase8s",
"access", "access",
"oracle", "oracle",
"h2", "h2",
@@ -74,7 +74,7 @@ const SCHEME_PROFILES: Record<string, ConnectionProfile> = {
gaussdb: { type: "gaussdb", profile: "gaussdb", label: "GaussDB", defaultPort: 5432 }, gaussdb: { type: "gaussdb", profile: "gaussdb", label: "GaussDB", defaultPort: 5432 },
kwdb: { type: "kwdb", profile: "kwdb", label: "KWDB", defaultPort: 26257 }, kwdb: { type: "kwdb", profile: "kwdb", label: "KWDB", defaultPort: 26257 },
gbase: { type: "gbase", profile: "gbase", label: "南大通用 GBase", defaultPort: 5258 }, gbase: { type: "gbase", profile: "gbase", label: "南大通用 GBase", defaultPort: 5258 },
"gbasedbt-sqli": { type: "gbase", profile: "gbase8s", label: "南大通用 GBase 8s", defaultPort: 9088 }, "gbasedbt-sqli": { type: "gbase8s", profile: "gbase8s", label: "南大通用 GBase 8s", defaultPort: 9088 },
"informix-sqli": { type: "informix", profile: "informix", label: "Informix", defaultPort: 9088 }, "informix-sqli": { type: "informix", profile: "informix", label: "Informix", defaultPort: 9088 },
yashandb: { type: "yashandb", profile: "yashandb", label: "YashanDB", defaultPort: 1688 }, yashandb: { type: "yashandb", profile: "yashandb", label: "YashanDB", defaultPort: 1688 },
opengauss: { type: "gaussdb", profile: "opengauss", label: "openGauss", defaultPort: 5432 }, opengauss: { type: "gaussdb", profile: "opengauss", label: "openGauss", defaultPort: 5432 },
@@ -599,7 +599,7 @@ function parseJdbcGbase8sUrl(source: string): ParsedConnectionUrl | null {
const host = match.groups.host.replace(/^\[/, "").replace(/\]$/, ""); const host = match.groups.host.replace(/^\[/, "").replace(/\]$/, "");
return { return {
dbType: "gbase", dbType: "gbase8s",
driverProfile: "gbase8s", driverProfile: "gbase8s",
driverLabel: "南大通用 GBase 8s", driverLabel: "南大通用 GBase 8s",
host, host,
@@ -301,8 +301,10 @@ function jdbcPrefixFor(config: ConnectionUrlCopyConfig): string | null {
return profile === GAUSSDB_M_JDBC_DRIVER_PROFILE ? "jdbc:gaussdb" : "jdbc:postgresql"; return profile === GAUSSDB_M_JDBC_DRIVER_PROFILE ? "jdbc:gaussdb" : "jdbc:postgresql";
case "iris": case "iris":
return profile === "cache" ? "jdbc:Cache" : "jdbc:IRIS"; return profile === "cache" ? "jdbc:Cache" : "jdbc:IRIS";
case "gbase8s":
return "jdbc:gbasedbt-sqli";
case "gbase": case "gbase":
return profile === "gbase8s" ? "jdbc:gbasedbt-sqli" : null; return null;
case "tdengine": case "tdengine":
return Number(config.port) === 6030 ? "jdbc:TAOS" : "jdbc:TAOS-RS"; return Number(config.port) === 6030 ? "jdbc:TAOS" : "jdbc:TAOS-RS";
default: default:
@@ -426,6 +428,7 @@ function buildSpecialJdbcUrl(config: ConnectionUrlCopyConfig, database: string,
return `jdbc:inceptor2://${formatHostForUrl(host)}${portPart}/${database || "default"}${props ? `;${props}` : ""}${ssl}`; return `jdbc:inceptor2://${formatHostForUrl(host)}${portPart}/${database || "default"}${props ? `;${props}` : ""}${ssl}`;
} }
case "informix": case "informix":
case "gbase8s":
case "gbase": { case "gbase": {
const prefix = config.db_type === "informix" ? "jdbc:informix-sqli" : jdbcPrefixFor(config); const prefix = config.db_type === "informix" ? "jdbc:informix-sqli" : jdbcPrefixFor(config);
if (!prefix) return config.db_type === "gbase" ? null : undefined; if (!prefix) return config.db_type === "gbase" ? null : undefined;
@@ -2,9 +2,9 @@ import type { ConnectionConfig } from "@/types/database";
/** /**
* GBase 8a / 8s / 8c share one picker entry and one version toggle, but they are * GBase 8a / 8s / 8c share one picker entry and one version toggle, but they are
* different engines: 8a and 8s are profiles of the `gbase` db type, while 8c is * different engines with different SQL dialects: 8a is the MySQL-based `gbase` type,
* PostgreSQL-shaped and keeps its own `gbase8c` db type (the same split as * 8s is Informix-derived (`gbase8s`) and 8c is PostgreSQL-shaped (`gbase8c`), the same
* OceanBase MySQL/Oracle mode). The toggle therefore switches `db_type` too. * split as OceanBase MySQL/Oracle mode. The toggle therefore switches `db_type` too.
*/ */
export const GBASE_PROFILES = ["gbase8a", "gbase8s", "gbase8c"] as const; export const GBASE_PROFILES = ["gbase8a", "gbase8s", "gbase8c"] as const;
@@ -16,5 +16,6 @@ export function isGbaseProfile(value: string | undefined | null): value is Gbase
export function gbaseProfileFromConfig(config: Pick<ConnectionConfig, "db_type" | "driver_profile">): GbaseProfile { export function gbaseProfileFromConfig(config: Pick<ConnectionConfig, "db_type" | "driver_profile">): GbaseProfile {
if (config.db_type === "gbase8c" || config.driver_profile === "gbase8c") return "gbase8c"; if (config.db_type === "gbase8c" || config.driver_profile === "gbase8c") return "gbase8c";
return config.driver_profile === "gbase8s" ? "gbase8s" : "gbase8a"; if (config.db_type === "gbase8s" || config.driver_profile === "gbase8s") return "gbase8s";
return "gbase8a";
} }
@@ -22,8 +22,8 @@ export function supportsCreateDatabaseCharset(databaseType?: DatabaseType, drive
// no charset clause in the SQL. The create dialog still offers a locale picker, and the chosen // no charset clause in the SQL. The create dialog still offers a locale picker, and the chosen
// value is applied by the gbase8s agent opening the CREATE DATABASE session with that DB_LOCALE. // value is applied by the gbase8s agent opening the CREATE DATABASE session with that DB_LOCALE.
// Plain Informix is excluded until InformixAgent learns to honor the locale directive. // Plain Informix is excluded until InformixAgent learns to honor the locale directive.
export function supportsCreateDatabaseLocale(databaseType?: DatabaseType, driverProfile?: string | null): boolean { export function supportsCreateDatabaseLocale(databaseType?: DatabaseType): boolean {
return databaseType === "gbase" && driverProfile === "gbase8s"; return databaseType === "gbase8s";
} }
export function buildCreateDatabaseSql(options: CreateDatabaseSqlOptions): Promise<string> { export function buildCreateDatabaseSql(options: CreateDatabaseSqlOptions): Promise<string> {
@@ -6,9 +6,7 @@ import { isSqlServerLinkedNode } from "@/lib/database/sqlServerLinkedServers";
import type { ConnectionConfig, TreeNode } from "@/types/database"; import type { ConnectionConfig, TreeNode } from "@/types/database";
export function canDropDatabaseNode(node: TreeNode, connection?: ConnectionConfig): boolean { export function canDropDatabaseNode(node: TreeNode, connection?: ConnectionConfig): boolean {
return ( return node.type === "database" && !!node.connectionId && !!node.database && !node.catalog && !isSqlServerLinkedNode(node) && !connectionIsEffectivelyReadOnly(connection) && (supportsDatabaseCreation(connection?.db_type) || supportsCreateDatabaseLocale(connection?.db_type));
node.type === "database" && !!node.connectionId && !!node.database && !node.catalog && !isSqlServerLinkedNode(node) && !connectionIsEffectivelyReadOnly(connection) && (supportsDatabaseCreation(connection?.db_type) || supportsCreateDatabaseLocale(connection?.db_type, connection?.driver_profile))
);
} }
export interface DatabaseBrowserMutation { export interface DatabaseBrowserMutation {
@@ -64,6 +64,8 @@ export const DATABASE_NAMESPACE_CREATION_MATRIX = {
"oceanbase-oracle": { deferred: "Oracle-mode schemas are users; use a dedicated user workflow" }, "oceanbase-oracle": { deferred: "Oracle-mode schemas are users; use a dedicated user workflow" },
questdb: { deferred: "single database model in DBX" }, questdb: { deferred: "single database model in DBX" },
gbase: { database: "schema" }, gbase: { database: "schema" },
// Informix-derived: no `CREATE SCHEMA <name>` (a schema is the table owner), but `CREATE DATABASE` works.
gbase8s: { connection: "database" },
// GBase 8c creates databases with an explicit DBCOMPATIBILITY mode that is fixed at // GBase 8c creates databases with an explicit DBCOMPATIBILITY mode that is fixed at
// creation time, so the generic PostgreSQL-shaped create path cannot pick a correct mode. // creation time, so the generic PostgreSQL-shaped create path cannot pick a correct mode.
gbase8c: { deferred: "database creation needs an explicit DBCOMPATIBILITY mode fixed at creation time" }, gbase8c: { deferred: "database creation needs an explicit DBCOMPATIBILITY mode fixed at creation time" },
@@ -108,13 +110,6 @@ export const DATABASE_NAMESPACE_CREATION_MATRIX = {
} satisfies Record<DatabaseType, DatabaseNamespaceCreationMatrixEntry>; } satisfies Record<DatabaseType, DatabaseNamespaceCreationMatrixEntry>;
function namespaceCreationMatrixEntry(connection: NonNullable<CreationConnection>): DatabaseNamespaceCreationMatrixEntry { function namespaceCreationMatrixEntry(connection: NonNullable<CreationConnection>): DatabaseNamespaceCreationMatrixEntry {
// GBase 8s shares `db_type: "gbase"` with the MySQL-based GBase 8a but is Informix-derived:
// it has no `CREATE SCHEMA <name>` (a schema is the table owner) yet does support
// `CREATE DATABASE`. Route it to the Informix-family semantics instead of the shared `gbase`
// entry, which is written for GBase 8a.
if (connection.db_type === "gbase" && connection.driver_profile === "gbase8s") {
return { connection: "database" };
}
return DATABASE_NAMESPACE_CREATION_MATRIX[connection.db_type]; return DATABASE_NAMESPACE_CREATION_MATRIX[connection.db_type];
} }
@@ -65,6 +65,7 @@ export const DATABASE_PROPERTY_EDITING_MATRIX = {
"oceanbase-oracle": { deferred: "Oracle-mode schemas are users; use a dedicated user workflow" }, "oceanbase-oracle": { deferred: "Oracle-mode schemas are users; use a dedicated user workflow" },
questdb: { deferred: "single database model in DBX" }, questdb: { deferred: "single database model in DBX" },
gbase: { deferred: "schema properties need product-specific handling" }, gbase: { deferred: "schema properties need product-specific handling" },
gbase8s: { deferred: "schema properties need product-specific handling" },
gbase8c: { deferred: "database and schema comment editing not verified for first pass" }, gbase8c: { deferred: "database and schema comment editing not verified for first pass" },
access: { deferred: "file-backed database properties are not edited in-place" }, access: { deferred: "file-backed database properties are not edited in-place" },
h2: { deferred: "schema properties need product-specific handling" }, h2: { deferred: "schema properties need product-specific handling" },
@@ -78,6 +78,7 @@ const NAVICAT_STYLE_TABLE_DATA_TYPES = new Set<DatabaseType>([
"questdb", "questdb",
"oceanbase-oracle", "oceanbase-oracle",
"gbase", "gbase",
"gbase8s",
"access", "access",
"h2", "h2",
"snowflake", "snowflake",
+6 -6
View File
@@ -92,7 +92,7 @@ export function jdbcConnectionUsesDriverRowOffset(connection: JdbcDialectConnect
export function effectiveDatabaseTypeForConnection(connection?: JdbcDialectConnection): DatabaseType | undefined { export function effectiveDatabaseTypeForConnection(connection?: JdbcDialectConnection): DatabaseType | undefined {
if (!connection) return undefined; if (!connection) return undefined;
if (connection.db_type === "gbase" && isGbase8sProfile(connection.driver_profile)) return "informix"; if (connection.db_type === "gbase8s") return "informix";
if (connection.db_type === "gbase") return "mysql"; if (connection.db_type === "gbase") return "mysql";
// MySQL-protocol connections to Doris/StarRocks (db_type=mysql with a // MySQL-protocol connections to Doris/StarRocks (db_type=mysql with a
// starrocks/doris driver_profile) must use the Doris/StarRocks SQL dialect so // starrocks/doris driver_profile) must use the Doris/StarRocks SQL dialect so
@@ -123,7 +123,7 @@ export function transferDatabaseTypeForConnection(connection?: JdbcDialectConnec
// the GBase driver manifest explicitly disables data transfer. Keep that // the GBase driver manifest explicitly disables data transfer. Keep that
// capability restriction attached to the raw connection type instead of // capability restriction attached to the raw connection type instead of
// exposing the mapped MySQL type to transfer callers. // exposing the mapped MySQL type to transfer callers.
if (connection?.db_type === "gbase") return undefined; if (connection?.db_type === "gbase" || connection?.db_type === "gbase8s") return undefined;
const effective = effectiveDatabaseTypeForConnection(connection); const effective = effectiveDatabaseTypeForConnection(connection);
if (effective === "doris" || effective === "starrocks") return connection?.db_type; if (effective === "doris" || effective === "starrocks") return connection?.db_type;
return effective; return effective;
@@ -136,7 +136,7 @@ export function connectionUsesConnectionRootSchemaMode(connection?: JdbcDialectC
export function connectionShouldLoadIdentifierQuote(connection: JdbcDialectConnection | undefined): boolean { export function connectionShouldLoadIdentifierQuote(connection: JdbcDialectConnection | undefined): boolean {
if (!connection) return false; if (!connection) return false;
if (connection.db_type === "gbase" && isGbase8sProfile(connection.driver_profile)) return true; if (connection.db_type === "gbase8s") return true;
if (connection.db_type === "kingbase") return true; if (connection.db_type === "kingbase") return true;
if (connection.db_type === "kyuubi") return true; if (connection.db_type === "kyuubi") return true;
// Cloud Spanner is dual-dialect: the agent reports a backtick for GoogleSQL and // Cloud Spanner is dual-dialect: the agent reports a backtick for GoogleSQL and
@@ -233,7 +233,7 @@ export function sqlSnippetDatabaseTypeForConnection(connection?: JdbcDialectConn
export function tableStructureDatabaseTypeForConnection(connection?: JdbcDialectConnection): DatabaseType | undefined { export function tableStructureDatabaseTypeForConnection(connection?: JdbcDialectConnection): DatabaseType | undefined {
if (!connection) return undefined; if (!connection) return undefined;
if (connection.db_type === "gbase" && !isGbase8sProfile(connection.driver_profile)) return "gbase"; if (connection.db_type === "gbase") return "gbase";
return effectiveDatabaseTypeForConnection(connection); return effectiveDatabaseTypeForConnection(connection);
} }
@@ -253,7 +253,7 @@ export function connectionUsesDatabaseObjectTreeMode(connection?: JdbcDialectCon
export function connectionShouldDiscoverJdbcSchemas(connection?: JdbcDialectConnection): boolean { export function connectionShouldDiscoverJdbcSchemas(connection?: JdbcDialectConnection): boolean {
// GBase 8s exposes owner schemas only when the current database can use them // GBase 8s exposes owner schemas only when the current database can use them
// in DML; non-ANSI databases fall back to the flat table tree. // in DML; non-ANSI databases fall back to the flat table tree.
if (connection?.db_type === "gbase" && isGbase8sProfile(connection.driver_profile)) return true; if (connection?.db_type === "gbase8s") return true;
return connection?.db_type === "jdbc" && !inferJdbcDialect(connection); return connection?.db_type === "jdbc" && !inferJdbcDialect(connection);
} }
@@ -348,7 +348,7 @@ export function connectionObjectTreeNodeSchema(connection: JdbcDialectConnection
/** GBase 8s reports the table owner as a schema, but does not accept it in table DML/DDL names. */ /** GBase 8s reports the table owner as a schema, but does not accept it in table DML/DDL names. */
export function connectionTableSqlSchema(connection: JdbcDialectConnection | undefined, schema?: string): string | undefined { export function connectionTableSqlSchema(connection: JdbcDialectConnection | undefined, schema?: string): string | undefined {
if (connection?.db_type === "gbase" && isGbase8sProfile(connection.driver_profile)) return undefined; if (connection?.db_type === "gbase8s") return undefined;
return schema; return schema;
} }
@@ -61,6 +61,7 @@ const SCHEMA_FIRST_QUALIFIER_TYPES = new Set<DatabaseType>([
"xugu", "xugu",
"oscar", "oscar",
"gbase", "gbase",
"gbase8s",
"saphana", "saphana",
"sqlserver", "sqlserver",
"snowflake", "snowflake",
@@ -238,7 +238,7 @@ export function filterDatabaseNamesForConnection(databaseNames: string[], connec
} }
export function filterDatabaseNamesForVisiblePicker(databaseNames: string[], connection: VisibleDatabaseConnection | undefined): string[] { export function filterDatabaseNamesForVisiblePicker(databaseNames: string[], connection: VisibleDatabaseConnection | undefined): string[] {
if (connection?.db_type === "gbase" && connection.driver_profile === "gbase8s") { if (connection?.db_type === "gbase8s") {
return databaseNames; return databaseNames;
} }
return databaseNames.filter((name) => !isSystemDatabaseName(effectiveDatabaseTypeForConnection(connection), name)); return databaseNames.filter((name) => !isSystemDatabaseName(effectiveDatabaseTypeForConnection(connection), name));
@@ -8,7 +8,6 @@ export function supportsSidebarDatabaseStorage(connection: ConnectionConfig | un
export function supportsSidebarTableStorage(connection: ConnectionConfig | undefined): boolean { export function supportsSidebarTableStorage(connection: ConnectionConfig | undefined): boolean {
if (!connection || !sidebarTableStorageTypes.has(connection.db_type)) return false; if (!connection || !sidebarTableStorageTypes.has(connection.db_type)) return false;
if (connection.db_type === "gbase" && connection.driver_profile === "gbase8s") return false;
return connection.db_type !== "postgres" || connection.driver_profile !== "cockroachdb"; return connection.db_type !== "postgres" || connection.driver_profile !== "cockroachdb";
} }
@@ -149,7 +149,7 @@ function gbase8sConnection(): ConnectionConfig {
...informixConnection(), ...informixConnection(),
id: "gbase8s-1", id: "gbase8s-1",
name: "GBase 8s", name: "GBase 8s",
db_type: "gbase", db_type: "gbase8s",
driver_profile: "gbase8s", driver_profile: "gbase8s",
database: "dbx_test", database: "dbx_test",
} as ConnectionConfig; } as ConnectionConfig;
@@ -64,7 +64,7 @@ export const CONNECTION_PROFILES = {
exasol: { type: "exasol", port: 8563, user: "sys", label: "Exasol", icon: "exasol" }, exasol: { type: "exasol", port: 8563, user: "sys", label: "Exasol", icon: "exasol" },
gbase: { type: "gbase", port: 5258, user: "gbasedbt", label: "南大通用 GBase 8a", icon: "gbase" }, gbase: { type: "gbase", port: 5258, user: "gbasedbt", label: "南大通用 GBase 8a", icon: "gbase" },
gbase8a: { type: "gbase", port: 5258, user: "gbasedbt", label: "南大通用 GBase 8a", icon: "gbase" }, gbase8a: { type: "gbase", port: 5258, user: "gbasedbt", label: "南大通用 GBase 8a", icon: "gbase" },
gbase8s: { type: "gbase", port: 9088, user: "gbasedbt", label: "南大通用 GBase 8s", icon: "gbase" }, gbase8s: { type: "gbase8s", port: 9088, user: "gbasedbt", label: "南大通用 GBase 8s", icon: "gbase" },
gbase8c: { type: "gbase8c", port: 5432, user: "gbase", label: "南大通用 GBase 8c", icon: "gbase" }, gbase8c: { type: "gbase8c", port: 5432, user: "gbase", label: "南大通用 GBase 8c", icon: "gbase" },
opengauss: { type: "opengauss", port: 5432, user: "gaussdb", label: "openGauss", icon: "opengauss" }, opengauss: { type: "opengauss", port: 5432, user: "gaussdb", label: "openGauss", icon: "opengauss" },
gaussdb: { type: "gaussdb", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" }, gaussdb: { type: "gaussdb", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" },
@@ -47,6 +47,7 @@ export const DATABASE_TYPES = [
"questdb", "questdb",
"oceanbase-oracle", "oceanbase-oracle",
"gbase", "gbase",
"gbase8s",
"gbase8c", "gbase8c",
"access", "access",
"h2", "h2",
@@ -1785,13 +1785,6 @@
"driverStoreVisible": true, "driverStoreVisible": true,
"driverStoreOrder": 15, "driverStoreOrder": 15,
"driverProfiles": [ "driverProfiles": [
{
"profile": "gbase8s",
"agentKey": "gbase8s",
"label": "南大通用 GBase 8s",
"storeVisible": true,
"storeOrder": 16
},
{ {
"profile": "gbase8a", "profile": "gbase8a",
"agentKey": "gbase8a", "agentKey": "gbase8a",
@@ -1828,6 +1821,47 @@
"driverManagement": true "driverManagement": true
} }
}, },
{
"dbType": "gbase8s",
"compareModes": [
"schema",
"data"
],
"label": "南大通用 GBase 8s",
"dialect": "Informix",
"runtimeMode": "agent",
"mcpMode": "bridge",
"agentKey": "gbase8s",
"driverStoreVisible": true,
"driverStoreOrder": 16,
"singleConnectionPool": false,
"metadataConnectionScoped": false,
"skipTcpProbe": true,
"defaultPort": 9088,
"traits": {
"schemaAware": true,
"treeSchema": true
},
"supportLevel": "operate",
"capabilities": {
"queryExecution": true,
"metadataBrowse": true,
"objectBrowser": true,
"objectSource": false,
"schemaSearch": true,
"diagram": true,
"tableDataEdit": true,
"tableStructureEdit": true,
"tableImport": false,
"dataTransfer": false,
"sqlFileExecution": true,
"databaseCreate": false,
"fieldLineage": false,
"sqlExplain": false,
"userAdmin": false,
"driverManagement": true
}
},
{ {
"dbType": "gbase8c", "dbType": "gbase8c",
"compareModes": [ "compareModes": [
+1
View File
@@ -332,6 +332,7 @@ macro_rules! agent_connection_pool_database_type {
| DatabaseType::Exasol | DatabaseType::Exasol
| DatabaseType::OceanbaseOracle | DatabaseType::OceanbaseOracle
| DatabaseType::Gbase | DatabaseType::Gbase
| DatabaseType::Gbase8s
| DatabaseType::Oracle | DatabaseType::Oracle
| DatabaseType::H2 | DatabaseType::H2
| DatabaseType::Snowflake | DatabaseType::Snowflake
+19 -25
View File
@@ -373,16 +373,10 @@ struct TableExportSqlContext<'a> {
schema: Option<&'a str>, schema: Option<&'a str>,
} }
fn table_export_sql_context<'a>( fn table_export_sql_context<'a>(database_type: DatabaseType, schema: Option<&'a str>) -> TableExportSqlContext<'a> {
database_type: DatabaseType,
driver_profile: Option<&str>,
schema: Option<&'a str>,
) -> TableExportSqlContext<'a> {
// GBase8s selects from the active database and rejects the owner-qualified // GBase8s selects from the active database and rejects the owner-qualified
// names produced for MySQL-family GBase connections. // names produced for other connection types.
if database_type == DatabaseType::Gbase if database_type == DatabaseType::Gbase8s {
&& driver_profile.is_some_and(|profile| profile.eq_ignore_ascii_case("gbase8s"))
{
TableExportSqlContext { database_type: DatabaseType::Informix, schema: None } TableExportSqlContext { database_type: DatabaseType::Informix, schema: None }
} else { } else {
TableExportSqlContext { database_type, schema } TableExportSqlContext { database_type, schema }
@@ -1612,15 +1606,15 @@ async fn export_table_data_core_inner(
cancelled: Arc<AtomicBool>, cancelled: Arc<AtomicBool>,
cancel_token: CancellationToken, cancel_token: CancellationToken,
) -> Result<(), String> { ) -> Result<(), String> {
// 1. Get database type and the profile that can refine its SQL dialect. // 1. Get database type
let (db_type, driver_profile) = { let db_type = {
let configs = state.configs.read().await; let configs = state.configs.read().await;
let config = configs let config = configs
.get(&request.connection_id) .get(&request.connection_id)
.ok_or_else(|| format!("Connection config not found: {}", request.connection_id))?; .ok_or_else(|| format!("Connection config not found: {}", request.connection_id))?;
(config.db_type, config.driver_profile.clone()) config.db_type
}; };
let sql_context = table_export_sql_context(db_type, driver_profile.as_deref(), request.schema.as_deref()); let sql_context = table_export_sql_context(db_type, request.schema.as_deref());
// 2. Get pool // 2. Get pool
let client_session_id = table_export_client_session_id(&request.export_id); let client_session_id = table_export_client_session_id(&request.export_id);
@@ -3059,7 +3053,7 @@ mod tests {
split_max_mb: None, split_max_mb: None,
omit_database_qualifier: None, omit_database_qualifier: None,
}; };
let context = table_export_sql_context(DatabaseType::Iotdb, None, request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Iotdb, request.schema.as_deref());
let columns = vec!["Time".to_string(), "root.test.device2.temperature".to_string()]; let columns = vec!["Time".to_string(), "root.test.device2.temperature".to_string()];
let query_columns = table_export_query_columns(&request, &context, &columns).unwrap(); let query_columns = table_export_query_columns(&request, &context, &columns).unwrap();
assert_eq!(query_columns.as_ref(), &["temperature".to_string()]); assert_eq!(query_columns.as_ref(), &["temperature".to_string()]);
@@ -3127,7 +3121,7 @@ mod tests {
split_max_mb: None, split_max_mb: None,
omit_database_qualifier: None, omit_database_qualifier: None,
}; };
let context = table_export_sql_context(DatabaseType::Iotdb, None, request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Iotdb, request.schema.as_deref());
let columns = vec!["tImE".to_string(), "temperature".to_string()]; let columns = vec!["tImE".to_string(), "temperature".to_string()];
let query_columns = table_export_query_columns(&request, &context, &columns).unwrap(); let query_columns = table_export_query_columns(&request, &context, &columns).unwrap();
@@ -3171,7 +3165,7 @@ mod tests {
split_max_mb: None, split_max_mb: None,
omit_database_qualifier: None, omit_database_qualifier: None,
}; };
let context = table_export_sql_context(DatabaseType::Iotdb, None, request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Iotdb, request.schema.as_deref());
let error = table_export_query_columns(&request, &context, &["TIME".to_string()]).unwrap_err(); let error = table_export_query_columns(&request, &context, &["TIME".to_string()]).unwrap_err();
assert_eq!(error, "IoTDB table export requires at least one non-Time column"); assert_eq!(error, "IoTDB table export requires at least one non-Time column");
} }
@@ -3210,7 +3204,7 @@ mod tests {
split_max_mb: None, split_max_mb: None,
omit_database_qualifier: None, omit_database_qualifier: None,
}; };
let context = table_export_sql_context(DatabaseType::Iotdb, None, request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Iotdb, request.schema.as_deref());
let columns = vec![ let columns = vec![
"Time".to_string(), "Time".to_string(),
"root.test.device2.temperature".to_string(), "root.test.device2.temperature".to_string(),
@@ -3271,7 +3265,7 @@ mod tests {
"SELECT \"Time\", \"value\" FROM \"samples\" LIMIT 25 OFFSET 10", "SELECT \"Time\", \"value\" FROM \"samples\" LIMIT 25 OFFSET 10",
), ),
] { ] {
let context = table_export_sql_context(database_type, None, None); let context = table_export_sql_context(database_type, None);
let query_columns = table_export_query_columns(&request, &context, &columns).unwrap(); let query_columns = table_export_query_columns(&request, &context, &columns).unwrap();
assert!(matches!(query_columns, Cow::Borrowed(_))); assert!(matches!(query_columns, Cow::Borrowed(_)));
assert_eq!(table_cursor_sql(&request, &context, query_columns.as_ref(), &[], &[]), cursor_sql); assert_eq!(table_cursor_sql(&request, &context, query_columns.as_ref(), &[], &[]), cursor_sql);
@@ -3316,7 +3310,7 @@ mod tests {
split_max_mb: None, split_max_mb: None,
omit_database_qualifier: None, omit_database_qualifier: None,
}; };
let context = table_export_sql_context(DatabaseType::Oracle, None, request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Oracle, request.schema.as_deref());
let sql = table_cursor_sql( let sql = table_cursor_sql(
&request, &request,
@@ -3371,7 +3365,7 @@ mod tests {
}; };
let columns = vec!["id".to_string(), "payload".to_string()]; let columns = vec!["id".to_string(), "payload".to_string()];
let primary_keys = vec!["id".to_string()]; let primary_keys = vec!["id".to_string()];
let context = table_export_sql_context(DatabaseType::Gbase, Some("gbase8s"), request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Gbase8s, request.schema.as_deref());
assert_eq!(context.database_type, DatabaseType::Informix); assert_eq!(context.database_type, DatabaseType::Informix);
assert_eq!(context.schema, None); assert_eq!(context.schema, None);
@@ -3399,7 +3393,7 @@ mod tests {
"SELECT COUNT(*) FROM orders" "SELECT COUNT(*) FROM orders"
); );
let regular_gbase = table_export_sql_context(DatabaseType::Gbase, Some("gbase8a"), request.schema.as_deref()); let regular_gbase = table_export_sql_context(DatabaseType::Gbase, request.schema.as_deref());
assert_eq!(regular_gbase.database_type, DatabaseType::Gbase); assert_eq!(regular_gbase.database_type, DatabaseType::Gbase);
assert_eq!(regular_gbase.schema, Some("gbasedbt")); assert_eq!(regular_gbase.schema, Some("gbasedbt"));
assert_eq!( assert_eq!(
@@ -3407,7 +3401,7 @@ mod tests {
"SELECT \"id\", \"payload\" FROM \"gbasedbt\".\"orders\" ORDER BY \"id\" LIMIT 50 OFFSET 100" "SELECT \"id\", \"payload\" FROM \"gbasedbt\".\"orders\" ORDER BY \"id\" LIMIT 50 OFFSET 100"
); );
let informix = table_export_sql_context(DatabaseType::Informix, None, request.schema.as_deref()); let informix = table_export_sql_context(DatabaseType::Informix, request.schema.as_deref());
assert_eq!(informix.database_type, DatabaseType::Informix); assert_eq!(informix.database_type, DatabaseType::Informix);
assert_eq!(informix.schema, Some("gbasedbt")); assert_eq!(informix.schema, Some("gbasedbt"));
assert_eq!( assert_eq!(
@@ -3452,7 +3446,7 @@ mod tests {
}; };
let columns = vec!["id".to_string(), "DisplayName".to_string()]; let columns = vec!["id".to_string(), "DisplayName".to_string()];
let primary_keys = vec!["id".to_string()]; let primary_keys = vec!["id".to_string()];
let context = table_export_sql_context(DatabaseType::Gaussdb, None, request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Gaussdb, request.schema.as_deref());
assert_eq!( assert_eq!(
table_cursor_sql(&request, &context, &columns, &[], &primary_keys), table_cursor_sql(&request, &context, &columns, &[], &primary_keys),
@@ -3516,7 +3510,7 @@ mod tests {
let columns = vec!["id".to_string(), "geom".to_string(), "name".to_string()]; let columns = vec!["id".to_string(), "geom".to_string(), "name".to_string()];
let column_types = vec![Some("int".to_string()), Some("geometry".to_string()), Some("varchar".to_string())]; let column_types = vec![Some("int".to_string()), Some("geometry".to_string()), Some("varchar".to_string())];
let primary_keys = vec!["id".to_string()]; let primary_keys = vec!["id".to_string()];
let context = table_export_sql_context(DatabaseType::Mysql, None, None); let context = table_export_sql_context(DatabaseType::Mysql, None);
let cursor_sql = table_cursor_sql(&request, &context, &columns, &column_types, &primary_keys); let cursor_sql = table_cursor_sql(&request, &context, &columns, &column_types, &primary_keys);
assert!(cursor_sql.contains("ST_SRID(`geom`), ':', HEX(ST_AsWKB(`geom`))")); assert!(cursor_sql.contains("ST_SRID(`geom`), ':', HEX(ST_AsWKB(`geom`))"));
@@ -3578,7 +3572,7 @@ mod tests {
split_max_mb: None, split_max_mb: None,
omit_database_qualifier: None, omit_database_qualifier: None,
}; };
let context = table_export_sql_context(DatabaseType::Oracle, None, request.schema.as_deref()); let context = table_export_sql_context(DatabaseType::Oracle, request.schema.as_deref());
let sql = table_cursor_sql(&request, &context, &columns, &[], &primary_keys); let sql = table_cursor_sql(&request, &context, &columns, &[], &primary_keys);
assert_eq!(sql, "SELECT \"ID\", \"NAME\" FROM \"APP\".\"USERS\""); assert_eq!(sql, "SELECT \"ID\", \"NAME\" FROM \"APP\".\"USERS\"");
@@ -534,6 +534,7 @@ fn schema_first_qualifier_type(db_type: &DatabaseType) -> bool {
| DatabaseType::Xugu | DatabaseType::Xugu
| DatabaseType::Oscar | DatabaseType::Oscar
| DatabaseType::Gbase | DatabaseType::Gbase
| DatabaseType::Gbase8s
| DatabaseType::SapHana | DatabaseType::SapHana
| DatabaseType::SqlServer | DatabaseType::SqlServer
| DatabaseType::Snowflake | DatabaseType::Snowflake
+1 -3
View File
@@ -7298,9 +7298,7 @@ async fn list_object_statistics_once(
) )
.await; .await;
} }
if db_config.as_ref().is_some_and(|config| { if db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::Gbase) {
config.db_type == DatabaseType::Gbase && config.driver_profile.as_deref() != Some("gbase8s")
}) {
let sql = gbase8a_object_statistics_sql(database); let sql = gbase8a_object_statistics_sql(database);
return agent_list_object_statistics( return agent_list_object_statistics(
client, client,
@@ -113,6 +113,7 @@ fn maps_agent_database_types_to_driver_keys() {
assert_eq!(agent_key(&DatabaseType::Exasol, None), Some("exasol")); assert_eq!(agent_key(&DatabaseType::Exasol, None), Some("exasol"));
assert_eq!(agent_key(&DatabaseType::OceanbaseOracle, None), Some("oceanbase-oracle")); assert_eq!(agent_key(&DatabaseType::OceanbaseOracle, None), Some("oceanbase-oracle"));
assert_eq!(agent_key(&DatabaseType::Gbase, None), Some("gbase8a")); assert_eq!(agent_key(&DatabaseType::Gbase, None), Some("gbase8a"));
assert_eq!(agent_key(&DatabaseType::Gbase8s, None), Some("gbase8s"));
assert_eq!(agent_key(&DatabaseType::Access, None), Some("access")); assert_eq!(agent_key(&DatabaseType::Access, None), Some("access"));
assert_eq!(agent_key(&DatabaseType::Oracle, None), Some("oracle")); assert_eq!(agent_key(&DatabaseType::Oracle, None), Some("oracle"));
assert_eq!(agent_key(&DatabaseType::Databend, None), Some("databend")); assert_eq!(agent_key(&DatabaseType::Databend, None), Some("databend"));
+1 -1
View File
@@ -551,7 +551,7 @@ mod tests {
assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, Some("oracle-10g")), Some("oracle")); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, Some("oracle-10g")), Some("oracle"));
assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, Some("oracle-legacy")), Some("oracle")); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, Some("oracle-legacy")), Some("oracle"));
assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, None), Some("oracle")); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Oracle, None), Some("oracle"));
assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Gbase, Some("gbase8s")), Some("gbase8s")); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Gbase8s, None), Some("gbase8s"));
assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Gbase, None), Some("gbase8a")); assert_eq!(AgentManager::db_type_to_agent_key(&DatabaseType::Gbase, None), Some("gbase8a"));
manager.stop_daemon_by_key("oracle").await; manager.stop_daemon_by_key("oracle").await;
manager.stop_daemon_by_key("gbase8s").await; manager.stop_daemon_by_key("gbase8s").await;
+1
View File
@@ -4803,6 +4803,7 @@ fn uses_keyless_row_predicate(database_type: Option<DatabaseType>) -> bool {
| DatabaseType::Questdb | DatabaseType::Questdb
| DatabaseType::OceanbaseOracle | DatabaseType::OceanbaseOracle
| DatabaseType::Gbase | DatabaseType::Gbase
| DatabaseType::Gbase8s
| DatabaseType::Access | DatabaseType::Access
| DatabaseType::H2 | DatabaseType::H2
| DatabaseType::Snowflake | DatabaseType::Snowflake
@@ -82,9 +82,9 @@ pub fn build_database_structure_search_sql(
tdengine_structure_search_sql(&options.database, &tdengine_like_pattern_literal(&term), limit) tdengine_structure_search_sql(&options.database, &tdengine_like_pattern_literal(&term), limit)
} }
DatabaseType::Snowflake => snowflake_structure_search_sql(options.schema.as_deref(), &pattern_literal, limit), DatabaseType::Snowflake => snowflake_structure_search_sql(options.schema.as_deref(), &pattern_literal, limit),
// gbase8s is Informix-compatible (see the table_structure_sql dialects) // GBase 8s is its own `Gbase8s` type (Informix-compatible, no information_schema)
// and has no information_schema; only MySQL-dialect Gbase 8a takes this path. // and falls through to the data search; only MySQL-dialect Gbase 8a takes this path.
DatabaseType::Gbase if !driver_profile_is_gbase8s(options.driver_profile.as_deref()) => { DatabaseType::Gbase => {
gbase_structure_search_sql(&options.database, options.schema.as_deref(), &pattern_literal, limit) gbase_structure_search_sql(&options.database, options.schema.as_deref(), &pattern_literal, limit)
} }
DatabaseType::Jdbc if driver_profile_is_mysql(options.driver_profile.as_deref()) => { DatabaseType::Jdbc if driver_profile_is_mysql(options.driver_profile.as_deref()) => {
@@ -117,10 +117,6 @@ fn driver_profile_is_postgres(profile: Option<&str>) -> bool {
profile.contains("postgres") || profile.contains("kingbase") || profile.contains("opengauss") profile.contains("postgres") || profile.contains("kingbase") || profile.contains("opengauss")
} }
fn driver_profile_is_gbase8s(profile: Option<&str>) -> bool {
profile.map(str::trim).is_some_and(|profile| profile.to_ascii_lowercase().contains("gbase8s"))
}
fn driver_profile_is_oracle(profile: Option<&str>) -> bool { fn driver_profile_is_oracle(profile: Option<&str>) -> bool {
let profile = profile.map(str::trim).unwrap_or("").to_ascii_lowercase(); let profile = profile.map(str::trim).unwrap_or("").to_ascii_lowercase();
profile.contains("oracle") || profile.contains("dameng") || profile.contains("dm") profile.contains("oracle") || profile.contains("dameng") || profile.contains("dm")
@@ -596,15 +592,15 @@ mod tests {
assert_eq!( assert_eq!(
build_database_structure_search_sql(DatabaseStructureSearchSqlOptions { build_database_structure_search_sql(DatabaseStructureSearchSqlOptions {
database_type: Some(DatabaseType::Gbase), database_type: Some(DatabaseType::Gbase8s),
driver_profile: Some("gbase8s".to_string()), driver_profile: None,
database: "app".to_string(), database: "app".to_string(),
schema: None, schema: None,
term: "email".to_string(), term: "email".to_string(),
limit: Some(10), limit: Some(10),
}), }),
None, None,
"gbase8s is Informix-compatible and must fall back to data search" "GBase 8s is Informix-compatible and must fall back to data search"
); );
} }
@@ -47,6 +47,7 @@ pub fn is_schema_aware(database_type: DatabaseType) -> bool {
| DatabaseType::OpenGauss | DatabaseType::OpenGauss
| DatabaseType::OceanbaseOracle | DatabaseType::OceanbaseOracle
| DatabaseType::Gbase | DatabaseType::Gbase
| DatabaseType::Gbase8s
| DatabaseType::Databend | DatabaseType::Databend
| DatabaseType::Jdbc | DatabaseType::Jdbc
| DatabaseType::H2 | DatabaseType::H2
@@ -134,7 +135,7 @@ pub fn pagination_strategy(database_type: Option<DatabaseType>, context: Paginat
Some(DatabaseType::Db2) => TablePaginationStrategy::Db2FetchFirst, Some(DatabaseType::Db2) => TablePaginationStrategy::Db2FetchFirst,
Some(DatabaseType::SqlServer) => TablePaginationStrategy::SqlServerTop, Some(DatabaseType::SqlServer) => TablePaginationStrategy::SqlServerTop,
Some(DatabaseType::Iris) => TablePaginationStrategy::IrisTop, Some(DatabaseType::Iris) => TablePaginationStrategy::IrisTop,
Some(DatabaseType::Informix) => TablePaginationStrategy::InformixFirst, Some(DatabaseType::Informix | DatabaseType::Gbase8s) => TablePaginationStrategy::InformixFirst,
Some(DatabaseType::Firebird) => TablePaginationStrategy::FirebirdRows, Some(DatabaseType::Firebird) => TablePaginationStrategy::FirebirdRows,
Some(DatabaseType::OceanbaseOracle) => TablePaginationStrategy::Rownum, Some(DatabaseType::OceanbaseOracle) => TablePaginationStrategy::Rownum,
Some(DatabaseType::Questdb) => TablePaginationStrategy::QuestDbLimit, Some(DatabaseType::Questdb) => TablePaginationStrategy::QuestDbLimit,
@@ -898,7 +898,7 @@ pub fn profile_for(db_type: DatabaseType) -> DdlDialectProfile {
ClickHouse => clickhouse_profile(), ClickHouse => clickhouse_profile(),
// Engines whose DROP TABLE grammar has no `IF EXISTS`. // Engines whose DROP TABLE grammar has no `IF EXISTS`.
Db2 | Informix | SapHana | Teradata => { Db2 | Informix | Gbase8s | SapHana | Teradata => {
let mut profile = conservative_ansi(db_type); let mut profile = conservative_ansi(db_type);
profile.drop_table_supports_if_exists = false; profile.drop_table_supports_if_exists = false;
profile profile
@@ -189,7 +189,7 @@ pub fn quote_table_identifier(database_type: Option<DatabaseType>, name: &str) -
Some(db_type) if uses_backtick_identifier_quoting(Some(db_type)) => { Some(db_type) if uses_backtick_identifier_quoting(Some(db_type)) => {
format!("`{}`", name.replace('`', "``")) format!("`{}`", name.replace('`', "``"))
} }
Some(DatabaseType::Informix) if is_simple_informix_identifier(name) => name.to_string(), Some(DatabaseType::Informix | DatabaseType::Gbase8s) if is_simple_informix_identifier(name) => name.to_string(),
// SUNDB folds unquoted identifiers to uppercase (its JDBC driver reports // SUNDB folds unquoted identifiers to uppercase (its JDBC driver reports
// storesUpperCaseIdentifiers=true / supportsMixedCaseIdentifiers=false), so // storesUpperCaseIdentifiers=true / supportsMixedCaseIdentifiers=false), so
// `select * from rs_salest_budget` resolves to RS_SALEST_BUDGET. Quoting the // `select * from rs_salest_budget` resolves to RS_SALEST_BUDGET. Quoting the
@@ -201,8 +201,11 @@ pub fn table_data_schema<'a>(
driver_profile: Option<&str>, driver_profile: Option<&str>,
schema: Option<&'a str>, schema: Option<&'a str>,
) -> Option<&'a str> { ) -> Option<&'a str> {
if database_type == Some(DatabaseType::Informix) // The frontend reports GBase 8s through the Informix dialect (see `connectionSqlDialect`), so
&& driver_profile.is_some_and(|profile| profile.eq_ignore_ascii_case("gbase8s")) // the owner-free rule applies to both the dialect-mapped and the native `gbase8s` type.
if database_type == Some(DatabaseType::Gbase8s)
|| (database_type == Some(DatabaseType::Informix)
&& driver_profile.is_some_and(|profile| profile.eq_ignore_ascii_case("gbase8s")))
{ {
None None
} else { } else {
@@ -599,7 +602,7 @@ pub fn uses_connection_identifier_quote(database_type: Option<DatabaseType>, ide
// Kyuubi normally uses Hive-family backticks, but a Trino-backed // Kyuubi normally uses Hive-family backticks, but a Trino-backed
// session reports the ANSI double quote through connection info. // session reports the ANSI double quote through connection info.
|| (database_type == Some(DatabaseType::Kyuubi) && identifier_quote.is_some()) || (database_type == Some(DatabaseType::Kyuubi) && identifier_quote.is_some())
|| (database_type == Some(DatabaseType::Informix) && identifier_quote.is_some()) || (matches!(database_type, Some(DatabaseType::Informix | DatabaseType::Gbase8s)) && identifier_quote.is_some())
|| (matches!(database_type, Some(DatabaseType::Gaussdb | DatabaseType::OpenGauss | DatabaseType::Postgres)) || (matches!(database_type, Some(DatabaseType::Gaussdb | DatabaseType::OpenGauss | DatabaseType::Postgres))
&& identifier_quote.is_some()) && identifier_quote.is_some())
} }
+16 -22
View File
@@ -298,12 +298,12 @@ fn build_create_database_statement(options: &CreateDatabaseSqlOptions) -> Result
quote_table_identifier(options.database_type, &options.name) quote_table_identifier(options.database_type, &options.name)
)); ));
} }
if is_informix_family(options.database_type, options.driver_profile.as_deref()) { if is_informix_family(options.database_type) {
// Informix / GBase 8s accept only a bare `CREATE DATABASE <name>`. The new database // Informix / GBase 8s accept only a bare `CREATE DATABASE <name>`. The new database
// inherits the instance default locale, and the MySQL `CHARACTER SET`/`COLLATE` // inherits the instance default locale, and the MySQL `CHARACTER SET`/`COLLATE`
// clauses are invalid syntax here. Database names are ordinary identifiers, so quote // clauses are invalid syntax here. Database names are ordinary identifiers, so quote
// them with the Informix rule (unquoted for simple identifiers) rather than the // them with the Informix rule (unquoted for simple identifiers) rather than the
// default double-quote path that `DatabaseType::Gbase` would otherwise take. // default double-quote path that other types would otherwise take.
let name = quote_table_identifier(Some(DatabaseType::Informix), &options.name); let name = quote_table_identifier(Some(DatabaseType::Informix), &options.name);
let locale = clean_sql_option(options.charset.as_deref()); let locale = clean_sql_option(options.charset.as_deref());
if locale.is_empty() { if locale.is_empty() {
@@ -325,23 +325,18 @@ fn build_create_database_statement(options: &CreateDatabaseSqlOptions) -> Result
Ok(format!("CREATE DATABASE {name} CHARACTER SET {charset}{collate_clause};")) Ok(format!("CREATE DATABASE {name} CHARACTER SET {charset}{collate_clause};"))
} }
/// Whether the connection belongs to the Informix family: standalone Informix, or GBase 8s /// Whether the connection belongs to the Informix family: standalone Informix, or GBase 8s.
/// (which shares `DatabaseType::Gbase` with the MySQL-based GBase 8a and is only distinguishable /// Informix-family servers accept `CREATE DATABASE` but have no `CREATE SCHEMA <name>`
/// through the `gbase8s` driver profile). Informix-family servers accept `CREATE DATABASE` but /// statement (a "schema" is the table owner), so the create targets differ from the MySQL-based
/// have no `CREATE SCHEMA <name>` statement (a "schema" is the table owner), so the create /// GBase 8a (`DatabaseType::Gbase`).
/// targets differ from the rest of the `Gbase` family. fn is_informix_family(database_type: Option<DatabaseType>) -> bool {
fn is_informix_family(database_type: Option<DatabaseType>, driver_profile: Option<&str>) -> bool { matches!(database_type, Some(DatabaseType::Informix | DatabaseType::Gbase8s))
match database_type {
Some(DatabaseType::Informix) => true,
Some(DatabaseType::Gbase) => driver_profile.is_some_and(|profile| profile.eq_ignore_ascii_case("gbase8s")),
_ => false,
}
} }
pub fn supports_create_database_target(database_type: Option<DatabaseType>, driver_profile: Option<&str>) -> bool { pub fn supports_create_database_target(database_type: Option<DatabaseType>, driver_profile: Option<&str>) -> bool {
// Informix / GBase 8s create namespaces with `CREATE DATABASE`, so they are valid targets // Informix / GBase 8s create namespaces with `CREATE DATABASE`, so they are valid targets
// even though they are absent from the explicit list below. // even though they are absent from the explicit list below.
if is_informix_family(database_type, driver_profile) { if is_informix_family(database_type) {
return true; return true;
} }
matches!( matches!(
@@ -1559,12 +1554,11 @@ mod tests {
#[test] #[test]
fn builds_informix_family_create_database_without_mysql_clause() { fn builds_informix_family_create_database_without_mysql_clause() {
// GBase 8s shares DatabaseType::Gbase with GBase 8a and is only identified by the // With no locale chosen, GBase 8s emits a bare, unquoted CREATE DATABASE.
// gbase8s driver profile; with no locale chosen it emits a bare, unquoted CREATE DATABASE.
assert_eq!( assert_eq!(
build_create_database_sql(CreateDatabaseSqlOptions { build_create_database_sql(CreateDatabaseSqlOptions {
database_type: Some(DatabaseType::Gbase), database_type: Some(DatabaseType::Gbase8s),
driver_profile: Some("gbase8s".to_string()), driver_profile: None,
target: None, target: None,
parent: None, parent: None,
name: "app_db".to_string(), name: "app_db".to_string(),
@@ -1596,8 +1590,8 @@ mod tests {
// because Informix cannot express a codeset in CREATE DATABASE. // because Informix cannot express a codeset in CREATE DATABASE.
assert_eq!( assert_eq!(
build_create_database_sql(CreateDatabaseSqlOptions { build_create_database_sql(CreateDatabaseSqlOptions {
database_type: Some(DatabaseType::Gbase), database_type: Some(DatabaseType::Gbase8s),
driver_profile: Some("gbase8s".to_string()), driver_profile: None,
target: None, target: None,
parent: None, parent: None,
name: "app_db".to_string(), name: "app_db".to_string(),
@@ -1637,8 +1631,8 @@ mod tests {
} }
#[test] #[test]
fn create_database_target_distinguishes_gbase_profiles() { fn create_database_target_distinguishes_gbase_types() {
assert!(supports_create_database_target(Some(DatabaseType::Gbase), Some("gbase8s"))); assert!(supports_create_database_target(Some(DatabaseType::Gbase8s), None));
assert!(!supports_create_database_target(Some(DatabaseType::Gbase), Some("gbase8a"))); assert!(!supports_create_database_target(Some(DatabaseType::Gbase), Some("gbase8a")));
assert!(!supports_create_database_target(Some(DatabaseType::Gbase), None)); assert!(!supports_create_database_target(Some(DatabaseType::Gbase), None));
assert!(supports_create_database_target(Some(DatabaseType::Informix), None)); assert!(supports_create_database_target(Some(DatabaseType::Informix), None));
@@ -147,11 +147,9 @@ pub(super) fn capabilities_for(
comment: true, comment: true,
..base ..base
}, },
// GBase 8s is Informix-compatible, unlike the rest of the `Gbase` // GBase 8s is Informix-compatible, unlike GBase 8a (`Gbase`), which speaks
// family (GBase 8a and friends) which speak MySQL wire protocol and // the MySQL wire protocol and SQL dialect.
// SQL dialect. `driver_profile` is the only signal that distinguishes Some(DatabaseType::Gbase8s) => {
// them (see the same check in `table_export.rs` / `table_select.rs`).
Some(DatabaseType::Gbase) if driver_profile.is_some_and(|profile| profile.eq_ignore_ascii_case("gbase8s")) => {
TableStructureCapabilities { TableStructureCapabilities {
dialect: StructureDialect::Informix, dialect: StructureDialect::Informix,
add_column: true, add_column: true,
@@ -2809,8 +2809,8 @@ fn gbase8a_allows_mysql_style_column_reorder() {
#[test] #[test]
fn gbase8s_uses_informix_ddl_not_mysql() { fn gbase8s_uses_informix_ddl_not_mysql() {
let result = build_create_table_sql(TableStructureSqlOptions { let result = build_create_table_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::Gbase), database_type: Some(DatabaseType::Gbase8s),
driver_profile: Some("gbase8s".to_string()), driver_profile: None,
schema: Some("gbasedbt".to_string()), schema: Some("gbasedbt".to_string()),
table_name: "issue8003_repro".to_string(), table_name: "issue8003_repro".to_string(),
columns: vec![ columns: vec![
+46
View File
@@ -887,6 +887,7 @@ impl<'de> Deserialize<'de> for ConnectionConfig {
{ {
let mut value = Value::deserialize(deserializer)?; let mut value = Value::deserialize(deserializer)?;
migrate_legacy_transport_layers(&mut value); migrate_legacy_transport_layers(&mut value);
migrate_legacy_gbase8s_profile(&mut value);
let data = ConnectionConfigData::deserialize(value).map_err(serde::de::Error::custom)?; let data = ConnectionConfigData::deserialize(value).map_err(serde::de::Error::custom)?;
if let Some(grouping) = &data.redis_key_grouping { if let Some(grouping) = &data.redis_key_grouping {
let mut ids = std::collections::HashSet::new(); let mut ids = std::collections::HashSet::new();
@@ -909,6 +910,20 @@ impl<'de> Deserialize<'de> for ConnectionConfig {
} }
} }
/// GBase 8s used to be the `gbase8s` driver profile of the MySQL-based `gbase` type. It is its own
/// `gbase8s` type now (Informix dialect), so saved and in-flight configs are rewritten on load.
fn migrate_legacy_gbase8s_profile(value: &mut Value) {
let Some(object) = value.as_object_mut() else {
return;
};
let is_gbase = object.get("db_type").and_then(Value::as_str) == Some("gbase");
let is_8s_profile =
object.get("driver_profile").and_then(Value::as_str).is_some_and(|p| p.eq_ignore_ascii_case("gbase8s"));
if is_gbase && is_8s_profile {
object.insert("db_type".to_string(), Value::String("gbase8s".to_string()));
}
}
fn migrate_legacy_transport_layers(value: &mut Value) { fn migrate_legacy_transport_layers(value: &mut Value) {
let Some(object) = value.as_object_mut() else { let Some(object) = value.as_object_mut() else {
return; return;
@@ -1324,6 +1339,7 @@ impl ConnectionConfig {
DatabaseType::Ignite => format!("ignite://{host}:{port}{db_part}"), DatabaseType::Ignite => format!("ignite://{host}:{port}{db_part}"),
DatabaseType::Ignite3 => format!("ignite3://{host}:{port}{db_part}"), DatabaseType::Ignite3 => format!("ignite3://{host}:{port}{db_part}"),
DatabaseType::Gbase => format!("gbase://{host}:{port}{db_part}"), DatabaseType::Gbase => format!("gbase://{host}:{port}{db_part}"),
DatabaseType::Gbase8s => format!("gbase8s://{host}:{port}{db_part}"),
DatabaseType::H2 => format!("h2://{host}:{port}{db_part}"), DatabaseType::H2 => format!("h2://{host}:{port}{db_part}"),
DatabaseType::Snowflake => format!("snowflake://{host}/{db_part}"), DatabaseType::Snowflake => format!("snowflake://{host}/{db_part}"),
DatabaseType::Trino => format!("trino://{host}:{port}{db_part}"), DatabaseType::Trino => format!("trino://{host}:{port}{db_part}"),
@@ -1555,6 +1571,9 @@ impl ConnectionConfig {
DatabaseType::Gbase => { DatabaseType::Gbase => {
format!("gbase://{}:{}@{host}:{port}{db_part}", username, password) format!("gbase://{}:{}@{host}:{port}{db_part}", username, password)
} }
DatabaseType::Gbase8s => {
format!("gbase8s://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::H2 => { DatabaseType::H2 => {
format!("h2://{}:{}@{host}:{port}{db_part}", username, password) format!("h2://{}:{}@{host}:{port}{db_part}", username, password)
} }
@@ -3131,6 +3150,33 @@ mod tests {
assert_eq!(database_info_from_protocol_value(&serde_json::json!({ "ok": true })), None); assert_eq!(database_info_from_protocol_value(&serde_json::json!({ "ok": true })), None);
} }
#[test]
fn legacy_gbase8s_profile_connections_load_as_the_gbase8s_type() {
let config = |db_type: &str, driver_profile: Option<&str>| {
let mut value = serde_json::json!({
"id": "id",
"name": "GBase",
"db_type": db_type,
"host": "localhost",
"port": 9088,
"username": "gbasedbt",
"password": "",
"database": "app"
});
if let Some(profile) = driver_profile {
value["driver_profile"] = serde_json::json!(profile);
}
serde_json::from_value::<ConnectionConfig>(value).unwrap()
};
assert_eq!(config("gbase", Some("gbase8s")).db_type, DatabaseType::Gbase8s);
assert_eq!(config("gbase", Some("GBASE8S")).db_type, DatabaseType::Gbase8s);
assert_eq!(config("gbase8s", Some("gbase8s")).db_type, DatabaseType::Gbase8s);
assert_eq!(config("gbase", Some("gbase8a")).db_type, DatabaseType::Gbase);
assert_eq!(config("gbase", None).db_type, DatabaseType::Gbase);
assert_eq!(config("informix", Some("gbase8s")).db_type, DatabaseType::Informix);
}
#[test] #[test]
fn default_schema_is_optional_and_round_trips() { fn default_schema_is_optional_and_round_trips() {
let base = serde_json::json!({ let base = serde_json::json!({
+1
View File
@@ -95,6 +95,7 @@ export const databaseSupport: DatabaseSupportItem[] = [
{ id: "xugu", name: "XuguDB", icon: "/icons/database/xugu.png", tone: "#84cc16" }, { id: "xugu", name: "XuguDB", icon: "/icons/database/xugu.png", tone: "#84cc16" },
{ id: "gbase", name: "GBase", icon: "/icons/database/gbase.png", tone: "#06b6d4" }, { id: "gbase", name: "GBase", icon: "/icons/database/gbase.png", tone: "#06b6d4" },
{ id: "gbase8c", name: "GBase 8c", icon: "/icons/database/gbase.png", tone: "#06b6d4" }, { id: "gbase8c", name: "GBase 8c", icon: "/icons/database/gbase.png", tone: "#06b6d4" },
{ id: "gbase8s", name: "GBase 8s", icon: "/icons/database/gbase.png", tone: "#06b6d4" },
{ id: "access", name: "Access", icon: "/icons/database/access.png", tone: "#a53346" }, { id: "access", name: "Access", icon: "/icons/database/access.png", tone: "#a53346" },
{ id: "h2", name: "H2", icon: "/icons/database/h2.svg", tone: "#f7a81b" }, { id: "h2", name: "H2", icon: "/icons/database/h2.svg", tone: "#f7a81b" },
{ id: "etcd", name: "Etcd", icon: "/icons/database/etcd.svg", tone: "#419eda" }, { id: "etcd", name: "Etcd", icon: "/icons/database/etcd.svg", tone: "#419eda" },
+5 -4
View File
@@ -51,13 +51,14 @@ test("maps GBase profiles to their matching agent driver keys", () => {
assert.equal(agentDriverInstallKey("gbase"), "gbase8a"); assert.equal(agentDriverInstallKey("gbase"), "gbase8a");
assert.equal(agentDriverInstallKey("gbase", "gbase"), "gbase8a"); assert.equal(agentDriverInstallKey("gbase", "gbase"), "gbase8a");
assert.equal(agentDriverInstallKey("gbase", "gbase8a"), "gbase8a"); assert.equal(agentDriverInstallKey("gbase", "gbase8a"), "gbase8a");
assert.equal(agentDriverInstallKey("gbase", "gbase8s"), "gbase8s"); assert.equal(agentDriverInstallKey("gbase8s"), "gbase8s");
assert.equal(agentDriverInstallKey("gbase8s", "gbase8s"), "gbase8s");
}); });
test("uses the selected GBase 8s agent for install hints", () => { test("uses the selected GBase 8s agent for install hints", () => {
assert.equal( assert.equal(
showAgentDriverInstallHint( showAgentDriverInstallHint(
"gbase", "gbase8s",
[ [
{ db_type: "gbase", installed: true }, { db_type: "gbase", installed: true },
{ db_type: "gbase8s", installed: false }, { db_type: "gbase8s", installed: false },
@@ -68,7 +69,7 @@ test("uses the selected GBase 8s agent for install hints", () => {
); );
assert.equal( assert.equal(
showAgentDriverInstallHint( showAgentDriverInstallHint(
"gbase", "gbase8s",
[ [
{ db_type: "gbase", installed: false }, { db_type: "gbase", installed: false },
{ db_type: "gbase8s", installed: true }, { db_type: "gbase8s", installed: true },
@@ -89,7 +90,7 @@ test("uses unified Oracle and selected profile keys for update hints", () => {
assert.equal(hasAgentDriverUpdate("oracle", [{ db_type: "oracle", installed: true, update_available: true }], "oracle-10g"), true); assert.equal(hasAgentDriverUpdate("oracle", [{ db_type: "oracle", installed: true, update_available: true }], "oracle-10g"), true);
assert.equal( assert.equal(
hasAgentDriverUpdate( hasAgentDriverUpdate(
"gbase", "gbase8s",
[ [
{ db_type: "gbase", installed: true, update_available: false }, { db_type: "gbase", installed: true, update_available: false },
{ db_type: "gbase8s", installed: true, update_available: true }, { db_type: "gbase8s", installed: true, update_available: true },
+1 -1
View File
@@ -416,7 +416,7 @@ test("parses Apache IoTDB JDBC URLs", () => {
test("parses GBase 8s JDBC URLs", () => { test("parses GBase 8s JDBC URLs", () => {
const parsed = parseConnectionUrl("jdbc:gbasedbt-sqli://gbasedbt:secret@gbase.example.com:20013/testdb:GBASEDBTSERVER=gbase01;CLIENT_LOCALE=zh_cn.utf8"); const parsed = parseConnectionUrl("jdbc:gbasedbt-sqli://gbasedbt:secret@gbase.example.com:20013/testdb:GBASEDBTSERVER=gbase01;CLIENT_LOCALE=zh_cn.utf8");
assert.equal(parsed.dbType, "gbase"); assert.equal(parsed.dbType, "gbase8s");
assert.equal(parsed.driverProfile, "gbase8s"); assert.equal(parsed.driverProfile, "gbase8s");
assert.equal(parsed.driverLabel, "南大通用 GBase 8s"); assert.equal(parsed.driverLabel, "南大通用 GBase 8s");
assert.equal(parsed.host, "gbase.example.com"); assert.equal(parsed.host, "gbase.example.com");
-5
View File
@@ -12,11 +12,6 @@ agentKey: gbase8a
driverStoreVisible: true driverStoreVisible: true
driverStoreOrder: 15 driverStoreOrder: 15
driverProfiles: driverProfiles:
- profile: gbase8s
agentKey: gbase8s
label: 南大通用 GBase 8s
storeVisible: true
storeOrder: 16
- profile: gbase8a - profile: gbase8a
agentKey: gbase8a agentKey: gbase8a
label: 南大通用 GBase 8a label: 南大通用 GBase 8a
+37
View File
@@ -0,0 +1,37 @@
schemaVersion: 1
order: 452
dbType: gbase8s
compareModes: [schema, data]
rustVariant: Gbase8s
label: 南大通用 GBase 8s
dialect: Informix
runtimeMode: agent
mcpMode: bridge
agentKey: gbase8s
driverStoreVisible: true
driverStoreOrder: 16
singleConnectionPool: false
metadataConnectionScoped: false
skipTcpProbe: true
defaultPort: 9088
traits:
schemaAware: true
treeSchema: true
supportLevel: operate
capabilities:
queryExecution: true
metadataBrowse: true
objectBrowser: true
objectSource: false
schemaSearch: true
diagram: true
tableDataEdit: true
tableStructureEdit: true
tableImport: false
dataTransfer: false
sqlFileExecution: true
databaseCreate: false
fieldLineage: false
sqlExplain: false
userAdmin: false
driverManagement: true
@@ -312,7 +312,7 @@ profiles:
port: 5258 port: 5258
user: gbasedbt user: gbasedbt
- id: gbase8s - id: gbase8s
dbType: gbase dbType: gbase8s
label: 南大通用 GBase 8s label: 南大通用 GBase 8s
icon: gbase icon: gbase
port: 9088 port: 9088