feat: add GaussDB ODBC support and flatten connection list

- Add GaussDB/openGauss support via ODBC (resolves #106)
- Use pg_catalog queries instead of information_schema for compatibility
- Share ODBC environment across drivers with auto ODBCSYSINI detection
- Fix ODBC error message false positive on "Driver" keyword match
- Merge connection dialog categories into a single flat list
- Remove duplicate DDL button from data grid footer
This commit is contained in:
t8y2
2026-05-07 02:18:20 +08:00
parent 969f441753
commit b79e5b53bc
21 changed files with 602 additions and 40 deletions
+19 -15
View File
@@ -138,7 +138,7 @@ const driverProfiles: Record<
icon: "opengauss",
urlParams: "sslmode=disable",
},
gaussdb: { type: "postgres", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" },
gaussdb: { type: "gaussdb", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" },
kingbase: { type: "postgres", port: 54321, user: "system", label: "KingBase", icon: "kingbase" },
vastbase: { type: "postgres", port: 5432, user: "vastbase", label: "Vastbase", icon: "vastbase" },
doris: { type: "mysql", port: 9030, user: "root", label: "Doris", icon: "doris", urlParams: "" },
@@ -323,9 +323,7 @@ const dbOptions = [
{ value: "elasticsearch", label: "Elasticsearch" },
{ value: "mariadb", label: "MariaDB" },
{ value: "dm", label: "DM (Dameng)" },
];
const mysqlCompat = [
{ value: "gaussdb", label: "GaussDB" },
{ value: "tidb", label: "TiDB" },
{ value: "oceanbase", label: "OceanBase" },
{ value: "goldendb", label: "GoldenDB" },
@@ -333,24 +331,16 @@ const mysqlCompat = [
{ value: "selectdb", label: "SelectDB" },
{ value: "starrocks", label: "StarRocks" },
{ value: "tdengine", label: "TDengine" },
{ value: "custom_mysql", label: "Custom" },
];
const pgCompat = [
{ value: "opengauss", label: "openGauss" },
{ value: "gaussdb", label: "GaussDB" },
{ value: "kingbase", label: "KingBase" },
{ value: "vastbase", label: "Vastbase" },
{ value: "redshift", label: "Redshift" },
{ value: "cockroachdb", label: "CockroachDB" },
{ value: "custom_postgres", label: "Custom" },
{ value: "custom_mysql", label: "Custom (MySQL)" },
{ value: "custom_postgres", label: "Custom (PostgreSQL)" },
];
const dbCategories = computed<DbCategory[]>(() => [
{ key: "mainstream", title: t("connection.mainstream"), options: dbOptions },
{ key: "mysql", title: `MySQL ${t("connection.compatible")}`, options: mysqlCompat },
{ key: "postgres", title: `PostgreSQL ${t("connection.compatible")}`, options: pgCompat },
]);
const dbCategories = computed<DbCategory[]>(() => [{ key: "all", title: "", options: dbOptions }]);
const filteredDbCategories = computed<DbCategory[]>(() => {
const keyword = dbSearchQuery.value.trim().toLowerCase();
@@ -854,6 +844,20 @@ async function browseDbFilePath() {
</p>
</div>
<div v-if="selectedType === 'gaussdb'" class="grid grid-cols-4 items-center gap-4">
<span />
<p class="col-span-3 text-xs text-muted-foreground">
{{ t("connection.gaussdbOdbcHint") }}
<a
href="https://support.huaweicloud.com/mgtg-dws/dws_01_0032.html"
target="_blank"
class="underline text-primary hover:text-primary/80"
>
{{ t("connection.gaussdbDownload") }}
</a>
</p>
</div>
<div v-if="form.db_type === 'oracle'" class="grid grid-cols-4 items-center gap-4">
<Label class="text-right text-xs">SYSDBA</Label>
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
+11 -2
View File
@@ -49,8 +49,17 @@ const props = defineProps<{
focusTableName?: string;
}>();
const SQL_TYPES: DatabaseType[] = ["mysql", "postgres", "sqlite", "sqlserver", "oracle", "redshift", "dameng"];
const SCHEMA_AWARE_TYPES: DatabaseType[] = ["postgres", "sqlserver", "oracle", "redshift", "dameng"];
const SQL_TYPES: DatabaseType[] = [
"mysql",
"postgres",
"sqlite",
"sqlserver",
"oracle",
"redshift",
"dameng",
"gaussdb",
];
const SCHEMA_AWARE_TYPES: DatabaseType[] = ["postgres", "sqlserver", "oracle", "redshift", "dameng", "gaussdb"];
const CARD_WIDTH = 270;
const COLUMN_ROW_HEIGHT = 24;
const CARD_HEADER_HEIGHT = 44;
+2 -1
View File
@@ -77,7 +77,8 @@ async function resolveSchema(connectionId: string, database: string): Promise<st
config?.db_type === "sqlserver" ||
config?.db_type === "oracle" ||
config?.db_type === "redshift" ||
config?.db_type === "dameng";
config?.db_type === "dameng" ||
config?.db_type === "gaussdb";
if (needsSchema) {
const schemas = await api.listSchemas(connectionId, database);
return schemas.includes("public") ? "public" : (schemas[0] ?? "");
-3
View File
@@ -1663,9 +1663,6 @@ function escapeAndHighlightKeywords(s: string): string {
<Button v-if="hasPendingChanges" variant="ghost" size="sm" class="h-5 text-xs" @click="discardChanges">
{{ t("grid.discard") }}
</Button>
<Button variant="ghost" size="sm" class="h-5 text-xs" @click="toggleDdl">
<Code2 class="w-3 h-3 mr-1" /> DDL
</Button>
</template>
<span class="ml-auto flex items-center gap-1">
@@ -54,7 +54,7 @@ type SearchTableTask = {
table: TableInfo;
};
const SCHEMA_AWARE_TYPES = new Set<DatabaseType>(["postgres", "sqlserver", "oracle", "redshift", "dameng"]);
const SCHEMA_AWARE_TYPES = new Set<DatabaseType>(["postgres", "sqlserver", "oracle", "redshift", "dameng", "gaussdb"]);
const SYSTEM_SCHEMAS = new Set([
"information_schema",
"pg_catalog",
+22 -4
View File
@@ -87,7 +87,16 @@ const emit = defineEmits<{
}>();
const sqlFileUnsupportedTypes = new Set(["redis", "mongodb", "elasticsearch"]);
const diagramSupportedTypes = new Set(["mysql", "postgres", "sqlite", "sqlserver", "oracle", "redshift", "dameng"]);
const diagramSupportedTypes = new Set([
"mysql",
"postgres",
"sqlite",
"sqlserver",
"oracle",
"redshift",
"dameng",
"gaussdb",
]);
const databaseSearchSupportedTypes = new Set([
"mysql",
"postgres",
@@ -98,6 +107,7 @@ const databaseSearchSupportedTypes = new Set([
"duckdb",
"clickhouse",
"dameng",
"gaussdb",
]);
const tableImportSupportedTypes = new Set([
"mysql",
@@ -111,6 +121,7 @@ const tableImportSupportedTypes = new Set([
"starrocks",
"redshift",
"dameng",
"gaussdb",
]);
const tableStructureSupportedTypes = new Set(["mysql", "postgres", "sqlite", "sqlserver"]);
const fieldLineageSupportedTypes = new Set([
@@ -121,6 +132,7 @@ const fieldLineageSupportedTypes = new Set([
"oracle",
"redshift",
"dameng",
"gaussdb",
]);
const isExportingDatabase = ref(false);
@@ -133,7 +145,13 @@ function quoteIdent(name: string): string {
}
function isSchemaAwareDbType(dbType?: DatabaseType): boolean {
return dbType === "postgres" || dbType === "oracle" || dbType === "sqlserver" || dbType === "dameng";
return (
dbType === "postgres" ||
dbType === "oracle" ||
dbType === "sqlserver" ||
dbType === "dameng" ||
dbType === "gaussdb"
);
}
function qualifiedTableName(tableName: string, schema?: string): string {
@@ -242,7 +260,7 @@ async function toggle() {
queryStore.updateSql(tab, node.label);
} else if (node.type === "database" && node.connectionId && node.database) {
const config = connectionStore.getConfig(node.connectionId);
if (config?.db_type === "postgres" || config?.db_type === "sqlserver") {
if (config?.db_type === "postgres" || config?.db_type === "sqlserver" || config?.db_type === "gaussdb") {
await connectionStore.loadSchemas(node.connectionId, node.database);
} else {
await connectionStore.loadTables(node.connectionId, node.database);
@@ -472,7 +490,7 @@ async function confirmDuplicateStructure() {
let sql: string;
if (dbType === "mysql") {
sql = `CREATE TABLE ${target} LIKE ${source};`;
} else if (dbType === "postgres" || dbType === "redshift") {
} else if (dbType === "postgres" || dbType === "redshift" || dbType === "gaussdb") {
sql = `CREATE TABLE ${target} (LIKE ${source} INCLUDING ALL);`;
} else if (dbType === "sqlserver") {
sql = `SELECT TOP 0 * INTO ${target} FROM ${source};`;
@@ -34,6 +34,7 @@ const SQL_TYPES: DatabaseType[] = [
"clickhouse",
"duckdb",
"dameng",
"gaussdb",
];
const sqlConnections = computed(() => store.connections.filter((c) => SQL_TYPES.includes(c.db_type)));
@@ -134,7 +135,8 @@ async function loadTables() {
config?.db_type === "postgres" ||
config?.db_type === "sqlserver" ||
config?.db_type === "oracle" ||
config?.db_type === "dameng";
config?.db_type === "dameng" ||
config?.db_type === "gaussdb";
if (needsSchema) {
const schemas = await api.listSchemas(sourceConnectionId.value, sourceDatabase.value);
sourceSchema.value = schemas.includes("public") ? "public" : (schemas[0] ?? "");
@@ -225,7 +227,8 @@ async function startTransfer() {
targetConfig?.db_type === "postgres" ||
targetConfig?.db_type === "sqlserver" ||
targetConfig?.db_type === "oracle" ||
targetConfig?.db_type === "dameng";
targetConfig?.db_type === "dameng" ||
targetConfig?.db_type === "gaussdb";
if (targetNeedsSchema && !targetSchema.value) {
try {
const schemas = await api.listSchemas(targetConnectionId.value, targetDatabase.value);
+7 -1
View File
@@ -14,7 +14,13 @@ export function useSchemaOptions() {
function isSchemaAware(connectionId: string): boolean {
const dbType = connectionStore.getConfig(connectionId)?.db_type;
return dbType === "postgres" || dbType === "sqlserver" || dbType === "oracle" || dbType === "dameng";
return (
dbType === "postgres" ||
dbType === "sqlserver" ||
dbType === "oracle" ||
dbType === "dameng" ||
dbType === "gaussdb"
);
}
async function loadSchemaOptions(connectionId: string, database: string) {
+2
View File
@@ -113,6 +113,8 @@ export default {
sshExposeLan: "Expose tunnel to LAN",
dmCompatHint: "Requires DM8 ODBC driver installed on your system.",
dmDownload: "Download from Dameng",
gaussdbOdbcHint: "Requires GaussDB ODBC driver installed on your system.",
gaussdbDownload: "Download from Huawei Cloud",
mongoLegacyHint:
"For older MongoDB servers, enter authSource=admin&authMechanism=SCRAM-SHA-1 here; add directConnection=true only when needed for direct standalone connections.",
compatible: "Compatible",
+2
View File
@@ -112,6 +112,8 @@ export default {
sshExposeLan: "允许局域网访问隧道",
dmCompatHint: "需要在系统上安装达梦 DM8 ODBC 驱动程序。",
dmDownload: "前往达梦官网下载",
gaussdbOdbcHint: "需要在系统上安装 GaussDB ODBC 驱动程序。",
gaussdbDownload: "前往华为云下载",
mongoLegacyHint:
"连接旧版 MongoDB 时,可在此填写 authSource=admin&authMechanism=SCRAM-SHA-1;直连单节点时可按需追加 directConnection=true。",
compatible: "兼容",
+2 -1
View File
@@ -26,7 +26,8 @@ export function qualifiedTableName(
(databaseType === "postgres" ||
databaseType === "oracle" ||
databaseType === "sqlserver" ||
databaseType === "dameng") &&
databaseType === "dameng" ||
databaseType === "gaussdb") &&
schema
) {
return `${quoteTableIdentifier(databaseType, schema)}.${quoteTableIdentifier(databaseType, tableName)}`;
+8 -1
View File
@@ -129,6 +129,7 @@ export const useConnectionStore = defineStore("connection", () => {
starrocks: "StarRocks",
redshift: "Redshift",
dameng: "DM (Dameng)",
gaussdb: "GaussDB",
};
return {
...config,
@@ -653,7 +654,13 @@ export const useConnectionStore = defineStore("connection", () => {
function isSchemaAwareDatabase(connectionId: string): boolean {
const dbType = getConfig(connectionId)?.db_type;
return dbType === "postgres" || dbType === "sqlserver" || dbType === "oracle" || dbType === "dameng";
return (
dbType === "postgres" ||
dbType === "sqlserver" ||
dbType === "oracle" ||
dbType === "dameng" ||
dbType === "gaussdb"
);
}
async function listCompletionTables(connectionId: string, database: string): Promise<SqlCompletionTable[]> {
+2 -1
View File
@@ -12,7 +12,8 @@ export type DatabaseType =
| "doris"
| "starrocks"
| "redshift"
| "dameng";
| "dameng"
| "gaussdb";
export interface ConnectionConfig {
id: string;