diff --git a/README.md b/README.md index e09ef55bc8..fe70bbbd7d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ + @@ -85,7 +86,7 @@ ### 40+ Databases, One Tool -MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. Agent/JDBC-oriented profiles extend DBX to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections. All in a single ~15 MB app. No bundled Chromium. +MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KWDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. Agent/JDBC-oriented profiles extend DBX to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections. All in a single ~15 MB app. No bundled Chromium. ### Query Editor @@ -293,7 +294,7 @@ DBX is 15 MB with no runtime dependencies (no Java, no Python). It includes AI a
What databases are supported? -MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. JDBC-oriented profiles extend support to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections. +MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KWDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. JDBC-oriented profiles extend support to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections.
diff --git a/README.zh-CN.md b/README.zh-CN.md index 56dcfc0074..d8245a7410 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -33,6 +33,7 @@ + @@ -85,7 +86,7 @@ ### 40+ 种数据库,一个工具搞定 -MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等数据库都能直接连接。Agent/JDBC 方向的配置还可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 和自定义 JDBC。全部装进约 15 MB 的应用里,不内嵌 Chromium。 +MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KWDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等数据库都能直接连接。Agent/JDBC 方向的配置还可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 和自定义 JDBC。全部装进约 15 MB 的应用里,不内嵌 Chromium。 ### 查询编辑器 @@ -293,7 +294,7 @@ DBX 仅 15 MB,无需运行时依赖(无需 Java、无需 Python)。AI 和
支持哪些数据库? -MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等。JDBC 方向配置可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 及自定义 JDBC 连接。 +MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KWDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等。JDBC 方向配置可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 及自定义 JDBC 连接。
diff --git a/apps/desktop/public/icons/database/kwdb.svg b/apps/desktop/public/icons/database/kwdb.svg new file mode 100644 index 0000000000..487775987f --- /dev/null +++ b/apps/desktop/public/icons/database/kwdb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 3a952293df..ff6f8ab153 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -281,6 +281,7 @@ const driverProfiles: Record< icon: "opengauss", }, gaussdb: { type: "gaussdb", port: 5432, user: "gaussdb", label: "GaussDB", icon: "gaussdb" }, + kwdb: { type: "kwdb", port: 26257, user: "root", label: "KWDB", icon: "kwdb" }, kingbase: { type: "kingbase", port: 54321, user: "system", label: "KingBase", icon: "kingbase" }, highgo: { type: "highgo", port: 5866, user: "highgo", label: "瀚高 HighGo", icon: "highgo" }, yashandb: { type: "yashandb", port: 1688, user: "sys", label: "崖山 YashanDB", icon: "yashandb" }, @@ -532,6 +533,7 @@ const sshPathSegments = computed(() => { function defaultDatabaseForProfile() { if (form.value.db_type === "redshift") return "dev"; if (form.value.db_type === "gaussdb") return "postgres"; + if (form.value.db_type === "kwdb") return "defaultdb"; if (selectedType.value === "cockroachdb") return "defaultdb"; if (form.value.db_type === "highgo") return "highgo"; if (form.value.db_type === "yashandb") return "yasdb"; @@ -580,6 +582,7 @@ const iconTypeMap: Record = { gbase: "gbase", opengauss: "opengauss", gaussdb: "gaussdb", + kwdb: "kwdb", kingbase: "kingbase", highgo: "highgo", yashandb: "yashandb", @@ -625,6 +628,7 @@ const dbOptions = [ { value: "mariadb", label: "MariaDB" }, { value: "dm", label: "DM (Dameng)" }, { value: "gaussdb", label: "GaussDB" }, + { value: "kwdb", label: "KWDB" }, { value: "tidb", label: "TiDB" }, { value: "oceanbase", label: "OceanBase" }, { value: "goldendb", label: "GoldenDB" }, @@ -710,6 +714,7 @@ const tlsCapableDatabaseTypes = new Set([ "postgres", "redshift", "gaussdb", + "kwdb", "opengauss", "redis", "clickhouse", @@ -742,7 +747,7 @@ const mysqlClientKeyPath = computed({ form.value.url_params = setUrlParam(next, "ssl-key", value); }, }); -const nativePostgresTlsDatabaseTypes = new Set(["postgres", "redshift", "gaussdb", "opengauss"]); +const nativePostgresTlsDatabaseTypes = new Set(["postgres", "redshift", "gaussdb", "kwdb", "opengauss"]); const supportsPostgresTlsOptions = computed(() => nativePostgresTlsDatabaseTypes.has(form.value.db_type)); const postgresTlsMode = computed({ get: () => { diff --git a/apps/desktop/src/components/icons/DatabaseIcon.vue b/apps/desktop/src/components/icons/DatabaseIcon.vue index 236f0edcc1..b460d8502b 100644 --- a/apps/desktop/src/components/icons/DatabaseIcon.vue +++ b/apps/desktop/src/components/icons/DatabaseIcon.vue @@ -29,6 +29,7 @@ const assetIcons: Record = { oceanbase: "oceanbase", opengauss: "opengauss", gaussdb: "gaussdb", + kwdb: "kwdb", kingbase: "kingbase", highgo: "highgo.png", goldendb: "goldendb.png", diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 7425c62612..f0c206a417 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -156,6 +156,7 @@ const activeSqlFormatDialect = computed(() => { case "mysql": return "mysql"; case "postgres": + case "kwdb": return "postgres"; case "sqlite": case "rqlite": @@ -168,7 +169,8 @@ const activeSqlFormatDialect = computed(() => { }); const editorDialect = computed<"mysql" | "postgres" | "sqlserver">(() => { - if (activeEffectiveDatabaseType.value === "postgres") return "postgres"; + if (activeEffectiveDatabaseType.value === "postgres" || activeEffectiveDatabaseType.value === "kwdb") + return "postgres"; if (activeEffectiveDatabaseType.value === "sqlserver") return "sqlserver"; return "mysql"; }); diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index 3f263eafeb..40317da3d5 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -181,6 +181,7 @@ const sourceDialect = computed<"mysql" | "postgres" | "sqlserver">(() => { if ( effectiveDatabaseType.value === "postgres" || effectiveDatabaseType.value === "gaussdb" || + effectiveDatabaseType.value === "kwdb" || effectiveDatabaseType.value === "opengauss" ) return "postgres"; @@ -192,10 +193,12 @@ const sourceFormatDialect = computed(() => { case "mysql": case "postgres": case "sqlite": - case "rqlite": case "sqlserver": return effectiveDatabaseType.value; + case "rqlite": + return "sqlite"; case "gaussdb": + case "kwdb": case "opengauss": return "postgres"; default: diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index d75dd5343c..51a00e94c8 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -307,6 +307,7 @@ function isPostgresIdentityType(dbType: string | undefined): boolean { return ( dbType === "postgres" || dbType === "gaussdb" || + dbType === "kwdb" || dbType === "opengauss" || dbType === "highgo" || dbType === "vastbase" || diff --git a/apps/desktop/src/lib/connectionPresentation.ts b/apps/desktop/src/lib/connectionPresentation.ts index 0e75534d22..1de3c97e77 100644 --- a/apps/desktop/src/lib/connectionPresentation.ts +++ b/apps/desktop/src/lib/connectionPresentation.ts @@ -92,6 +92,7 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string { case "postgres": case "gaussdb": + case "kwdb": case "yashandb": case "redshift": return "postgresql://user:password@host:port/database"; diff --git a/apps/desktop/src/lib/connectionUrl.ts b/apps/desktop/src/lib/connectionUrl.ts index 26504da5f3..e7921c37f7 100644 --- a/apps/desktop/src/lib/connectionUrl.ts +++ b/apps/desktop/src/lib/connectionUrl.ts @@ -41,6 +41,7 @@ const SCHEME_PROFILES: Record = { dm: { type: "dameng", profile: "dm", label: "DM (Dameng)", defaultPort: 5236 }, dameng: { type: "dameng", profile: "dm", label: "DM (Dameng)", defaultPort: 5236 }, gaussdb: { type: "gaussdb", profile: "gaussdb", label: "GaussDB", defaultPort: 5432 }, + kwdb: { type: "kwdb", profile: "kwdb", label: "KWDB", defaultPort: 26257 }, gbase: { type: "gbase", profile: "gbase", label: "GBase", defaultPort: 5258 }, "gbasedbt-sqli": { type: "gbase", profile: "gbase8s", label: "GBase 8s", defaultPort: 9088 }, yashandb: { type: "yashandb", profile: "yashandb", label: "YashanDB", defaultPort: 1688 }, @@ -143,7 +144,7 @@ function urlParamsRequireTls(dbType: DatabaseType, params: string): boolean { return sslMode === "required" || sslMode === "require" || sslMode === "verify_ca" || sslMode === "verify_identity"; } - if (dbType === "postgres" || dbType === "redshift") { + if (dbType === "postgres" || dbType === "redshift" || dbType === "kwdb") { const sslMode = (queryParamValue(params, "sslmode") || "").toLowerCase(); return sslMode === "require" || sslMode === "verify-ca" || sslMode === "verify-full"; } diff --git a/apps/desktop/src/lib/databaseCapabilitySets.ts b/apps/desktop/src/lib/databaseCapabilitySets.ts index c3500588d7..723d93849d 100644 --- a/apps/desktop/src/lib/databaseCapabilitySets.ts +++ b/apps/desktop/src/lib/databaseCapabilitySets.ts @@ -7,6 +7,7 @@ export const SCHEMA_AWARE_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", @@ -43,6 +44,7 @@ export const DIAGRAM_SUPPORTED_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", @@ -75,6 +77,7 @@ export const DATABASE_SEARCH_SUPPORTED_TYPES = new Set([ "clickhouse", "dameng", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", @@ -120,6 +123,7 @@ export const TABLE_IMPORT_SUPPORTED_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", @@ -142,6 +146,7 @@ export const TABLE_STRUCTURE_SUPPORTED_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", @@ -162,6 +167,7 @@ export const CREATE_DATABASE_SUPPORTED_TYPES = new Set([ "oracle", "dameng", "gaussdb", + "kwdb", "opengauss", "oceanbase-oracle", "doris", @@ -179,6 +185,7 @@ export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "kwdb", "opengauss", "oceanbase-oracle", ]); @@ -199,6 +206,7 @@ export const TREE_SCHEMA_TYPES = new Set([ "sqlserver", "db2", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", @@ -222,7 +230,7 @@ export const TREE_SCHEMA_TYPES = new Set([ export const DATABASE_OBJECT_TREE_TYPES = new Set(["jdbc"]); -export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "gaussdb", "opengauss"]); +export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "gaussdb", "kwdb", "opengauss"]); export const AGENT_DRIVER_TYPES = new Set([ "dameng", @@ -270,6 +278,7 @@ export const TRANSFER_SQL_TYPES = new Set([ "duckdb", "dameng", "gaussdb", + "kwdb", "opengauss", "oceanbase-oracle", ]); @@ -284,6 +293,7 @@ export const DIAGRAM_SQL_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "kwdb", "opengauss", "oceanbase-oracle", ]); diff --git a/apps/desktop/src/lib/databaseTableDataCapabilities.ts b/apps/desktop/src/lib/databaseTableDataCapabilities.ts index 7aa80195fd..b2ce37035d 100644 --- a/apps/desktop/src/lib/databaseTableDataCapabilities.ts +++ b/apps/desktop/src/lib/databaseTableDataCapabilities.ts @@ -58,6 +58,7 @@ const NAVICAT_STYLE_TABLE_DATA_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", diff --git a/apps/desktop/src/lib/dbeaverImport.ts b/apps/desktop/src/lib/dbeaverImport.ts index a966d3510e..d3cb88a528 100644 --- a/apps/desktop/src/lib/dbeaverImport.ts +++ b/apps/desktop/src/lib/dbeaverImport.ts @@ -48,6 +48,7 @@ const profileMap: Record = { dameng: { dbType: "dameng", profile: "dm", label: "DM (Dameng)", port: 5236, user: "SYSDBA" }, dm: { dbType: "dameng", profile: "dm", label: "DM (Dameng)", port: 5236, user: "SYSDBA" }, gaussdb: { dbType: "gaussdb", profile: "gaussdb", label: "GaussDB", port: 5432, user: "gaussdb" }, + kwdb: { dbType: "kwdb", profile: "kwdb", label: "KWDB", port: 26257, user: "root" }, opengauss: { dbType: "gaussdb", profile: "opengauss", label: "openGauss", port: 5432, user: "gaussdb" }, }; diff --git a/apps/desktop/src/lib/objectRenameSql.ts b/apps/desktop/src/lib/objectRenameSql.ts index eb7205fc0d..233daeb42a 100644 --- a/apps/desktop/src/lib/objectRenameSql.ts +++ b/apps/desktop/src/lib/objectRenameSql.ts @@ -15,6 +15,7 @@ const postgresLikeRenameTypes = new Set([ "postgres", "redshift", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", diff --git a/apps/desktop/src/lib/objectSourceEditor.ts b/apps/desktop/src/lib/objectSourceEditor.ts index e3c9c531b7..7fac0cc876 100644 --- a/apps/desktop/src/lib/objectSourceEditor.ts +++ b/apps/desktop/src/lib/objectSourceEditor.ts @@ -19,6 +19,7 @@ const postgresLikeRoutineRenameTypes = new Set([ "postgres", "redshift", "gaussdb", + "kwdb", "kingbase", "highgo", "vastbase", diff --git a/apps/desktop/src/lib/tableStructureCapabilities.ts b/apps/desktop/src/lib/tableStructureCapabilities.ts index 2fb2eee386..d388f7dc4b 100644 --- a/apps/desktop/src/lib/tableStructureCapabilities.ts +++ b/apps/desktop/src/lib/tableStructureCapabilities.ts @@ -216,6 +216,7 @@ const capabilityByType: Partial sundb: mysqlCapabilities, postgres: postgresCapabilities, gaussdb: postgresCapabilities, + kwdb: postgresCapabilities, opengauss: postgresCapabilities, redshift: redshiftCapabilities, highgo: postgresCapabilities, diff --git a/apps/desktop/src/lib/tableStructureEditorState.ts b/apps/desktop/src/lib/tableStructureEditorState.ts index c5963b5795..f1528643e8 100644 --- a/apps/desktop/src/lib/tableStructureEditorState.ts +++ b/apps/desktop/src/lib/tableStructureEditorState.ts @@ -237,6 +237,7 @@ const DATA_TYPE_OPTION_ALIASES: Partial> = { goldendb: "mysql", sundb: "mysql", gaussdb: "postgres", + kwdb: "postgres", opengauss: "postgres", redshift: "postgres", highgo: "postgres", @@ -298,6 +299,7 @@ export function parseExtraToColumnExtra(extra: string | null | undefined, databa } else if ( databaseType === "postgres" || databaseType === "gaussdb" || + databaseType === "kwdb" || databaseType === "opengauss" || databaseType === "highgo" || databaseType === "vastbase" || @@ -405,6 +407,7 @@ function isTemporalPrecisionType(dbType: DatabaseType | undefined, baseType: str return ["time", "datetime", "timestamp"].includes(normalized); case "postgres": case "gaussdb": + case "kwdb": case "opengauss": case "highgo": case "vastbase": diff --git a/apps/desktop/src/lib/visibleDatabases.ts b/apps/desktop/src/lib/visibleDatabases.ts index 914c25eef0..f2694baae2 100644 --- a/apps/desktop/src/lib/visibleDatabases.ts +++ b/apps/desktop/src/lib/visibleDatabases.ts @@ -8,6 +8,7 @@ const SYSTEM_DATABASE_RULES: Partial>> gbase: new Set(["information_schema", "mysql", "performance_schema", "sys"]), postgres: new Set(["template0", "template1"]), gaussdb: new Set(["template0", "template1"]), + kwdb: new Set(["template0", "template1"]), opengauss: new Set(["template0", "template1"]), kingbase: new Set(["template0", "template1"]), highgo: new Set(["template0", "template1"]), diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 58982bb259..b98b2f6244 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -243,6 +243,7 @@ export const useConnectionStore = defineStore("connection", () => { redshift: "Redshift", dameng: "DM (Dameng)", gaussdb: "GaussDB", + kwdb: "KWDB", kingbase: "KingBase", highgo: "瀚高 HighGo", yashandb: "崖山 YashanDB", @@ -266,6 +267,8 @@ export const useConnectionStore = defineStore("connection", () => { let dbType = config.db_type; if ((profile === "gaussdb" || profile === "opengauss") && dbType === "postgres") { dbType = "gaussdb" as ConnectionConfig["db_type"]; + } else if (profile === "kwdb" && dbType === "postgres") { + dbType = "kwdb" as ConnectionConfig["db_type"]; } else if (profile === "redshift" && dbType === "postgres") { dbType = "redshift" as ConnectionConfig["db_type"]; } else if (profile === "kingbase" && dbType === "postgres") { diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index d7ee4f10f8..b2ca689621 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -689,7 +689,7 @@ export const useQueryStore = defineStore("query", () => { const dbType = conn?.db_type || ""; let schema = analysis.schema || tab.schema; if (!schema) { - if (dbType === "postgres") schema = "public"; + if (dbType === "postgres" || dbType === "kwdb") schema = "public"; else schema = ""; } const metadataSchema = diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index c43b3ea047..168da867f5 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -19,6 +19,7 @@ export type DatabaseType = | "highgo" | "vastbase" | "goldendb" + | "kwdb" | "yashandb" | "databricks" | "saphana" diff --git a/crates/dbx-core/assets/database-drivers.manifest.json b/crates/dbx-core/assets/database-drivers.manifest.json index 521c625baa..098691366d 100644 --- a/crates/dbx-core/assets/database-drivers.manifest.json +++ b/crates/dbx-core/assets/database-drivers.manifest.json @@ -210,6 +210,16 @@ "skipTcpProbe": false, "defaultPort": 8000 }, + { + "dbType": "kwdb", + "label": "KWDB", + "runtimeMode": "native", + "mcpMode": "direct", + "singleConnectionPool": false, + "metadataConnectionScoped": false, + "skipTcpProbe": false, + "defaultPort": 26257 + }, { "dbType": "yashandb", "label": "崖山 YashanDB", diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index 7c9ffd3c1b..069d689ae3 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -329,9 +329,11 @@ impl AppState { }; PoolKind::Mysql(pool, MysqlMode::Bare) } - DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb | DatabaseType::OpenGauss => { - PoolKind::Postgres(db::postgres::connect(&url, connect_timeout).await?) - } + DatabaseType::Postgres + | DatabaseType::Redshift + | DatabaseType::Gaussdb + | DatabaseType::Kwdb + | DatabaseType::OpenGauss => PoolKind::Postgres(db::postgres::connect(&url, connect_timeout).await?), DatabaseType::Sqlite => { let extensions = db::sqlite::sqlite_extension_specs_from_url_params(db_config.url_params.as_deref()) .into_iter() @@ -1011,9 +1013,10 @@ fn external_driver_connect_timeout(config: &ConnectionConfig) -> std::time::Dura fn native_postgres_url_config(config: &ConnectionConfig) -> Option { match config.db_type { - DatabaseType::Gaussdb | DatabaseType::OpenGauss => { + DatabaseType::Gaussdb | DatabaseType::Kwdb | DatabaseType::OpenGauss => { let mut normalized = config.clone(); - if config.db_type == DatabaseType::Gaussdb { + normalized.database = normalized.effective_database().map(str::to_string); + if matches!(config.db_type, DatabaseType::Gaussdb | DatabaseType::Kwdb) { let params = normalized.url_params.as_deref().unwrap_or("").trim().trim_start_matches('?'); if !params.to_lowercase().contains("sslmode=") { normalized.url_params = Some(if params.is_empty() { @@ -1124,6 +1127,7 @@ mod tests { use crate::agent_manager::{AgentState, JavaRuntimeConfig, JavaRuntimeMode, DEFAULT_JRE_KEY}; use crate::db; use crate::models::connection::{default_connect_timeout_secs, ConnectionConfig, DatabaseType, ProxyType}; + use crate::query; use crate::schema; use crate::storage::Storage; @@ -1613,6 +1617,39 @@ mod tests { ); } + #[test] + fn kwdb_endpoint_url_uses_postgres_scheme_for_native_driver() { + let mut config = mysql_config(None); + config.db_type = DatabaseType::Kwdb; + config.username = "root".to_string(); + config.password = "secret".to_string(); + config.port = 26257; + + assert_eq!( + connection_url_for_endpoint(&config, &config.host, config.port), + "postgres://root:secret@127.0.0.1:26257/defaultdb?sslmode=disable" + ); + assert_eq!( + redacted_connection_url_for_endpoint(&config, &config.host, config.port), + "postgres://127.0.0.1:26257/defaultdb?sslmode=disable" + ); + } + + #[test] + fn kwdb_endpoint_url_keeps_explicit_sslmode() { + let mut config = mysql_config(None); + config.db_type = DatabaseType::Kwdb; + config.username = "root".to_string(); + config.password = "secret".to_string(); + config.port = 26257; + config.url_params = Some("sslmode=require&application_name=dbx".to_string()); + + assert_eq!( + connection_url_for_endpoint(&config, &config.host, config.port), + "postgres://root:secret@127.0.0.1:26257/defaultdb?sslmode=require&application_name=dbx" + ); + } + #[test] fn opengauss_endpoint_url_uses_postgres_scheme_for_native_driver() { let mut config = mysql_config(Some("postgres")); @@ -1739,6 +1776,7 @@ mod tests { DatabaseType::ClickHouse, DatabaseType::SqlServer, DatabaseType::Elasticsearch, + DatabaseType::Kwdb, ] { let mut config = mysql_config(Some("app")); config.db_type = db_type; @@ -1933,4 +1971,121 @@ mod tests { )) .await; } + + #[tokio::test] + #[ignore = "requires a reachable KWDB instance via environment variables"] + async fn live_kwdb_native_connection_succeeds() { + let host = std::env::var("DBX_TEST_KWDB_HOST").expect("DBX_TEST_KWDB_HOST not set"); + let port = std::env::var("DBX_TEST_KWDB_PORT") + .expect("DBX_TEST_KWDB_PORT not set") + .parse::() + .expect("DBX_TEST_KWDB_PORT should be a u16"); + let username = std::env::var("DBX_TEST_KWDB_USER").unwrap_or_else(|_| "root".to_string()); + let password = std::env::var("DBX_TEST_KWDB_PASSWORD").unwrap_or_default(); + let database = std::env::var("DBX_TEST_KWDB_DATABASE").unwrap_or_else(|_| "defaultdb".to_string()); + let url_params = std::env::var("DBX_TEST_KWDB_URL_PARAMS").unwrap_or_else(|_| "sslmode=disable".to_string()); + + let mut config = mysql_config(Some(&database)); + config.id = "kwdb-live".to_string(); + config.db_type = DatabaseType::Kwdb; + config.host = host; + config.port = port; + config.username = username; + config.password = password; + config.url_params = Some(url_params); + + let (state, dir) = test_app_state().await; + state.configs.write().await.insert(config.id.clone(), config); + let pool_key = state.get_or_create_pool("kwdb-live", None).await.unwrap(); + let pool = { + let connections = state.connections.read().await; + match connections.get(&pool_key).expect("KWDB pool should be created") { + PoolKind::Postgres(pool) => pool.clone(), + _ => panic!("KWDB should use the PostgreSQL pool path"), + } + }; + let result = query::execute_sql_statement( + &state, + "kwdb-live", + &database, + "SELECT current_database(), current_schema()", + None, + None, + ) + .await + .unwrap_or_else(|err| panic!("failed to query live KWDB: {err}")); + assert_eq!(result.rows.len(), 1); + let database_column_index = result + .columns + .iter() + .position(|column| column == "current_database") + .expect("current_database column should be present"); + assert_eq!(result.rows[0].get(database_column_index).and_then(|value| value.as_str()), Some(database.as_str())); + let databases = schema::list_databases_core(&state, "kwdb-live").await.unwrap(); + assert!(databases.iter().any(|database| database.name == "defaultdb")); + let test_schema = "dbx_kwdb_live"; + db::postgres::execute_query(&pool, &format!("DROP SCHEMA IF EXISTS {test_schema} CASCADE")) + .await + .unwrap_or_else(|err| panic!("failed to clean KWDB test schema: {err}")); + query::execute_sql_statement( + &state, + "kwdb-live", + &database, + &format!("CREATE SCHEMA {test_schema}"), + None, + None, + ) + .await + .unwrap_or_else(|err| panic!("failed to create KWDB test schema: {err}")); + query::execute_sql_statement( + &state, + "kwdb-live", + &database, + "CREATE TABLE devices (id INT PRIMARY KEY, name STRING, active BOOL)", + Some(test_schema), + None, + ) + .await + .unwrap_or_else(|err| panic!("failed to create KWDB test table: {err}")); + query::execute_sql_statement( + &state, + "kwdb-live", + &database, + "INSERT INTO devices (id, name, active) VALUES (1, 'meter-a', true)", + Some(test_schema), + None, + ) + .await + .unwrap_or_else(|err| panic!("failed to insert KWDB test row: {err}")); + let query_result = query::execute_sql_statement( + &state, + "kwdb-live", + &database, + "SELECT name, active FROM devices WHERE id = 1", + Some(test_schema), + None, + ) + .await + .unwrap_or_else(|err| panic!("failed to query KWDB test row: {err}")); + assert_eq!(query_result.rows.len(), 1); + assert_eq!(query_result.rows[0].first().and_then(|value| value.as_str()), Some("meter-a")); + + let schemas = schema::list_schemas_core(&state, "kwdb-live", &database).await.unwrap(); + assert!(schemas.iter().any(|schema| schema == test_schema)); + let tables = schema::list_tables_core(&state, "kwdb-live", &database, test_schema, None, None).await.unwrap(); + assert!(tables.iter().any(|table| table.name == "devices" && table.table_type == "BASE TABLE")); + let columns = schema::get_columns_core(&state, "kwdb-live", &database, test_schema, "devices").await.unwrap(); + let id_column = columns.iter().find(|column| column.name == "id").expect("id column should be listed"); + assert!(id_column.data_type.to_lowercase().contains("int")); + let name_column = columns.iter().find(|column| column.name == "name").expect("name column should be listed"); + assert!(name_column.data_type.to_lowercase().contains("text")); + let active_column = + columns.iter().find(|column| column.name == "active").expect("active column should be listed"); + assert!(active_column.data_type.to_lowercase().contains("bool")); + db::postgres::execute_query(&pool, &format!("DROP SCHEMA {test_schema} CASCADE")) + .await + .unwrap_or_else(|err| panic!("failed to drop KWDB test schema: {err}")); + pool.close(); + let _ = std::fs::remove_dir_all(dir); + } } diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 7be2821414..476b98fc52 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -1242,6 +1242,7 @@ fn uses_keyless_row_predicate(database_type: Option) -> bool { | DatabaseType::Redshift | DatabaseType::Dameng | DatabaseType::Gaussdb + | DatabaseType::Kwdb | DatabaseType::Kingbase | DatabaseType::Highgo | DatabaseType::Vastbase diff --git a/crates/dbx-core/src/db_admin_sql.rs b/crates/dbx-core/src/db_admin_sql.rs index c96c98d64d..b8b664b526 100644 --- a/crates/dbx-core/src/db_admin_sql.rs +++ b/crates/dbx-core/src/db_admin_sql.rs @@ -174,6 +174,7 @@ pub fn build_drop_table_child_object_sql(options: DropTableChildObjectSqlOptions Some( DatabaseType::Postgres | DatabaseType::Gaussdb + | DatabaseType::Kwdb | DatabaseType::OpenGauss | DatabaseType::Highgo | DatabaseType::Vastbase @@ -203,6 +204,7 @@ pub fn build_drop_table_child_object_sql(options: DropTableChildObjectSqlOptions Some( DatabaseType::Postgres | DatabaseType::Gaussdb + | DatabaseType::Kwdb | DatabaseType::OpenGauss | DatabaseType::Highgo | DatabaseType::Vastbase @@ -254,7 +256,7 @@ pub fn build_create_schema_sql(options: SchemaNameSqlOptions) -> String { pub fn build_drop_schema_sql(options: SchemaNameSqlOptions) -> String { let schema = quote_table_identifier(options.database_type, &options.name); - if matches!(options.database_type, Some(DatabaseType::Postgres | DatabaseType::Gaussdb)) { + if matches!(options.database_type, Some(DatabaseType::Postgres | DatabaseType::Gaussdb | DatabaseType::Kwdb)) { format!("DROP SCHEMA {schema} CASCADE;") } else { format!("DROP SCHEMA {schema};") @@ -359,6 +361,7 @@ fn is_postgres_like_rename(database_type: DatabaseType) -> bool { DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb + | DatabaseType::Kwdb | DatabaseType::Kingbase | DatabaseType::Highgo | DatabaseType::Vastbase @@ -372,7 +375,11 @@ fn is_oracle_like_rename(database_type: DatabaseType) -> bool { fn is_postgres_like_structure_copy(database_type: DatabaseType) -> bool { matches!( database_type, - DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb | DatabaseType::OpenGauss + DatabaseType::Postgres + | DatabaseType::Redshift + | DatabaseType::Gaussdb + | DatabaseType::Kwdb + | DatabaseType::OpenGauss ) } @@ -564,7 +571,7 @@ mod tests { ); assert_eq!( build_drop_schema_sql(SchemaNameSqlOptions { - database_type: Some(DatabaseType::Gaussdb), + database_type: Some(DatabaseType::Kwdb), name: "analytics".to_string(), }), "DROP SCHEMA \"analytics\" CASCADE;" @@ -654,7 +661,7 @@ mod tests { ); assert_eq!( build_duplicate_table_structure_sql(DuplicateTableStructureSqlOptions { - database_type: Some(DatabaseType::Postgres), + database_type: Some(DatabaseType::Kwdb), schema: Some("public".to_string()), source_name: "users".to_string(), target_name: "users_copy".to_string(), diff --git a/crates/dbx-core/src/models/connection.rs b/crates/dbx-core/src/models/connection.rs index 1ebc236782..399b5a0c95 100644 --- a/crates/dbx-core/src/models/connection.rs +++ b/crates/dbx-core/src/models/connection.rs @@ -193,6 +193,7 @@ pub enum DatabaseType { Vastbase, Goldendb, Gaussdb, + Kwdb, Yashandb, Databricks, #[serde(rename = "saphana")] @@ -304,6 +305,7 @@ impl ConnectionConfig { DatabaseType::ClickHouse => Some("default"), DatabaseType::Rqlite => Some("main"), DatabaseType::Gaussdb | DatabaseType::OpenGauss => Some("postgres"), + DatabaseType::Kwdb => Some("defaultdb"), DatabaseType::Kingbase | DatabaseType::Vastbase => Some("postgres"), DatabaseType::Highgo => Some("highgo"), DatabaseType::Yashandb => Some("yasdb"), @@ -424,6 +426,7 @@ impl ConnectionConfig { DatabaseType::Vastbase => format!("vastbase://{host}:{port}{db_part}"), DatabaseType::Goldendb => format!("goldendb://{host}:{port}{db_part}"), DatabaseType::Gaussdb => format!("gaussdb://{host}:{port}{db_part}"), + DatabaseType::Kwdb => format!("kwdb://{host}:{port}{db_part}"), DatabaseType::Yashandb => format!("yashandb://{host}:{port}{db_part}"), DatabaseType::Databricks => format!("databricks://{host}:{port}{db_part}"), DatabaseType::SapHana => format!("saphana://{host}:{port}{db_part}"), @@ -549,6 +552,9 @@ impl ConnectionConfig { DatabaseType::Gaussdb => { format!("gaussdb://{}:{}@{host}:{port}{db_part}", username, password) } + DatabaseType::Kwdb => { + format!("kwdb://{}:{}@{host}:{port}{db_part}", username, password) + } DatabaseType::Yashandb => { format!("yashandb://{}:{}@{host}:{port}{db_part}", username, password) } @@ -1618,6 +1624,14 @@ mod tests { assert_eq!(config.connection_url(), "gaussdb://gaussdb:secret@10.1.2.3:2883/postgres"); } + #[test] + fn kwdb_url_defaults_to_defaultdb_database() { + let mut config = mysql_config("root", "secret", None); + config.db_type = DatabaseType::Kwdb; + + assert_eq!(config.connection_url(), "kwdb://root:secret@10.1.2.3:2883/defaultdb"); + } + #[test] fn yashandb_url_defaults_to_yasdb_database() { let mut config = mysql_config("sys", "secret", None); diff --git a/crates/dbx-core/src/object_source_sql.rs b/crates/dbx-core/src/object_source_sql.rs index b3cecf3c2e..baddb7a21e 100644 --- a/crates/dbx-core/src/object_source_sql.rs +++ b/crates/dbx-core/src/object_source_sql.rs @@ -125,8 +125,10 @@ pub fn build_executable_object_source_statements(input: EditableObjectSourceSqlI return Ok(vec![replace_sqlserver_create_with_create_or_alter(source)]); } - if matches!(input.database_type, DatabaseType::Postgres | DatabaseType::Gaussdb | DatabaseType::OpenGauss) - && input.object_type == ObjectSourceKind::View + if matches!( + input.database_type, + DatabaseType::Postgres | DatabaseType::Gaussdb | DatabaseType::Kwdb | DatabaseType::OpenGauss + ) && input.object_type == ObjectSourceKind::View { return Ok(vec![format!( "CREATE OR REPLACE VIEW {} AS\n{}", @@ -211,6 +213,7 @@ fn is_postgres_like(database_type: DatabaseType) -> bool { DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb + | DatabaseType::Kwdb | DatabaseType::OpenGauss | DatabaseType::Kingbase | DatabaseType::Highgo @@ -447,19 +450,21 @@ mod tests { } #[test] - fn opengauss_view_body_opens_as_create_or_replace_view() { - let sql = build_executable_object_source_sql(EditableObjectSourceSqlInput { - database_type: DatabaseType::OpenGauss, - object_type: ObjectSourceKind::View, - schema: Some("public".to_string()), - name: "active users".to_string(), - source: " SELECT id, name FROM users WHERE active ".to_string(), - }) - .unwrap(); - assert_eq!( - sql, - "CREATE OR REPLACE VIEW \"public\".\"active users\" AS\nSELECT id, name FROM users WHERE active;" - ); + fn postgres_compatible_view_body_opens_as_create_or_replace_view() { + for database_type in [DatabaseType::OpenGauss, DatabaseType::Kwdb] { + let sql = build_executable_object_source_sql(EditableObjectSourceSqlInput { + database_type, + object_type: ObjectSourceKind::View, + schema: Some("public".to_string()), + name: "active users".to_string(), + source: " SELECT id, name FROM users WHERE active ".to_string(), + }) + .unwrap(); + assert_eq!( + sql, + "CREATE OR REPLACE VIEW \"public\".\"active users\" AS\nSELECT id, name FROM users WHERE active;" + ); + } } #[test] diff --git a/crates/dbx-core/src/sql_dialect.rs b/crates/dbx-core/src/sql_dialect.rs index bf9fe11acd..b3d2ac0a04 100644 --- a/crates/dbx-core/src/sql_dialect.rs +++ b/crates/dbx-core/src/sql_dialect.rs @@ -345,6 +345,7 @@ pub fn is_schema_aware(database_type: DatabaseType) -> bool { | DatabaseType::Redshift | DatabaseType::Dameng | DatabaseType::Gaussdb + | DatabaseType::Kwdb | DatabaseType::Kingbase | DatabaseType::Highgo | DatabaseType::Vastbase @@ -408,6 +409,7 @@ mod tests { #[test] fn qualifies_schema_only_for_schema_aware_databases() { assert_eq!(qualified_table_name(Some(DatabaseType::Postgres), Some("public"), "users"), "\"public\".\"users\""); + assert_eq!(qualified_table_name(Some(DatabaseType::Kwdb), Some("public"), "users"), "\"public\".\"users\""); assert_eq!(qualified_table_name(Some(DatabaseType::Mysql), Some("public"), "users"), "`users`"); assert_eq!(qualified_table_name(Some(DatabaseType::Jdbc), Some("cbsdw_dwd"), "dwd_test_df"), "dwd_test_df"); } diff --git a/crates/dbx-core/src/table_structure_sql.rs b/crates/dbx-core/src/table_structure_sql.rs index a6b6069ca0..b730ba3fb3 100644 --- a/crates/dbx-core/src/table_structure_sql.rs +++ b/crates/dbx-core/src/table_structure_sql.rs @@ -211,6 +211,7 @@ fn capabilities_for(database_type: Option) -> TableStructureCapabi Some( DatabaseType::Postgres | DatabaseType::Gaussdb + | DatabaseType::Kwdb | DatabaseType::OpenGauss | DatabaseType::Highgo | DatabaseType::Vastbase diff --git a/crates/dbx-core/tests/database_capabilities.rs b/crates/dbx-core/tests/database_capabilities.rs index d26af273e1..9cec252ac4 100644 --- a/crates/dbx-core/tests/database_capabilities.rs +++ b/crates/dbx-core/tests/database_capabilities.rs @@ -85,6 +85,7 @@ fn classifies_agent_database_types() { assert!(!is_agent_type(&DatabaseType::Mysql)); assert!(!is_agent_type(&DatabaseType::Jdbc)); assert!(!is_agent_type(&DatabaseType::Gaussdb)); + assert!(!is_agent_type(&DatabaseType::Kwdb)); assert!(!is_agent_type(&DatabaseType::OpenGauss)); } @@ -102,6 +103,7 @@ fn identifies_single_connection_pool_types() { assert!(is_single_connection_pool(&DatabaseType::Jdbc)); assert!(!is_single_connection_pool(&DatabaseType::Trino)); assert!(!is_single_connection_pool(&DatabaseType::Postgres)); + assert!(!is_single_connection_pool(&DatabaseType::Kwdb)); } #[test] @@ -110,6 +112,7 @@ fn identifies_metadata_connections_that_drop_database_scope() { assert!(!is_metadata_connection_scoped(&DatabaseType::Doris)); assert!(!is_metadata_connection_scoped(&DatabaseType::StarRocks)); assert!(!is_metadata_connection_scoped(&DatabaseType::Postgres)); + assert!(!is_metadata_connection_scoped(&DatabaseType::Kwdb)); assert!(!is_metadata_connection_scoped(&DatabaseType::Oracle)); } @@ -129,6 +132,7 @@ fn skips_tcp_probe_for_local_file_plugin_and_agent_types() { assert!(!skips_tcp_probe(&DatabaseType::Postgres)); assert!(!skips_tcp_probe(&DatabaseType::Mysql)); assert!(!skips_tcp_probe(&DatabaseType::Gaussdb)); + assert!(!skips_tcp_probe(&DatabaseType::Kwdb)); assert!(!skips_tcp_probe(&DatabaseType::OpenGauss)); } diff --git a/docs/content/docs/databases.cn.mdx b/docs/content/docs/databases.cn.mdx index eb5f60d321..3668d08070 100644 --- a/docs/content/docs/databases.cn.mdx +++ b/docs/content/docs/databases.cn.mdx @@ -13,10 +13,10 @@ DBX 主应用会尽量使用 Rust 原生驱动。部分数据库通过 MySQL 或 | 分组 | 类型 | 说明 | |---|---|---| -| 核心 SQL 数据库 | MySQL、PostgreSQL、SQLite、DuckDB、ClickHouse、SQL Server、Oracle、Redshift、DM、GaussDB | 直接出现在 DBX 连接选择器中 | +| 核心 SQL 数据库 | MySQL、PostgreSQL、SQLite、DuckDB、ClickHouse、SQL Server、Oracle、Redshift、DM、GaussDB、KWDB | 直接出现在 DBX 连接选择器中 | | 文档与键值数据库 | Redis、MongoDB、Elasticsearch | 根据数据模型使用专门浏览器或查询流程,不完全等同于关系型表格 | | MySQL 兼容类型 | MariaDB、TiDB、OceanBase、Doris、SelectDB、StarRocks、GoldenDB、自定义 MySQL | 复用 MySQL 风格连接能力 | -| PostgreSQL 兼容类型 | openGauss、KingBase、HighGo、Vastbase、CockroachDB、自定义 PostgreSQL | 复用 PostgreSQL 风格连接能力 | +| PostgreSQL 兼容类型 | KWDB、openGauss、KingBase、HighGo、Vastbase、CockroachDB、自定义 PostgreSQL | 复用 PostgreSQL 风格连接能力 | | 文件型数据库 | SQLite、DuckDB、Microsoft Access | 选择本地数据库文件,不填写主机和端口 | | Agent/JDBC 扩展类型 | H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、TDengine、虚谷 XuguDB、JDBC | 功能覆盖取决于对应驱动路径 | @@ -53,6 +53,7 @@ DBX 主应用会尽量使用 Rust 原生驱动。部分数据库通过 MySQL 或 | SunDB | 22000 | | TDengine | 6041 | | 虚谷 XuguDB | 5138 | +| KWDB | 26257 | SQLite、DuckDB、Access 和 JDBC 在不需要网络端口时,会在连接模型中保存为端口 `0`。 @@ -64,14 +65,14 @@ DBX 只会在具备足够元数据和 SQL 生成能力的数据库上启用高 | 功能 | 支持类型 | |---|---| -| Schema 感知树 | PostgreSQL、SQL Server、Oracle、Redshift、DM、GaussDB、KingBase、HighGo、Vastbase、JDBC、H2、Snowflake、Trino、DB2、TDengine、虚谷 XuguDB | -| ER 图 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DM、GaussDB、KingBase、HighGo、Vastbase、GoldenDB、Access、H2、DB2 | -| 数据库搜索 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DuckDB、ClickHouse、DM、GaussDB、KingBase、HighGo、Vastbase、GoldenDB、Access、H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、TDengine、虚谷 XuguDB | -| 表数据导入 | MySQL、PostgreSQL、SQLite、DuckDB、ClickHouse、SQL Server、Oracle、Doris、StarRocks、Redshift、DM、GaussDB、KingBase、HighGo、Vastbase、GoldenDB、Access | -| 表结构编辑器 | MySQL、PostgreSQL、SQLite、SQL Server | -| 创建数据库 | MySQL、PostgreSQL、SQL Server、ClickHouse、Oracle、DM、GaussDB、Doris、StarRocks、Redshift | -| 字段血缘 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DM、GaussDB | -| 数据传输 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、ClickHouse、DuckDB、DM、GaussDB | +| Schema 感知树 | PostgreSQL、SQL Server、Oracle、Redshift、DM、GaussDB、KWDB、KingBase、HighGo、Vastbase、JDBC、H2、Snowflake、Trino、DB2、TDengine、虚谷 XuguDB | +| ER 图 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DM、GaussDB、KWDB、KingBase、HighGo、Vastbase、GoldenDB、Access、H2、DB2 | +| 数据库搜索 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DuckDB、ClickHouse、DM、GaussDB、KWDB、KingBase、HighGo、Vastbase、GoldenDB、Access、H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、TDengine、虚谷 XuguDB | +| 表数据导入 | MySQL、PostgreSQL、SQLite、DuckDB、ClickHouse、SQL Server、Oracle、Doris、StarRocks、Redshift、DM、GaussDB、KWDB、KingBase、HighGo、Vastbase、GoldenDB、Access | +| 表结构编辑器 | MySQL、PostgreSQL、SQLite、SQL Server、KWDB | +| 创建数据库 | MySQL、PostgreSQL、SQL Server、ClickHouse、Oracle、DM、GaussDB、KWDB、Doris、StarRocks、Redshift | +| 字段血缘 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DM、GaussDB、KWDB | +| 数据传输 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、ClickHouse、DuckDB、DM、GaussDB、KWDB | | 不支持 SQL 文件执行 | Redis、MongoDB、Elasticsearch | @@ -82,8 +83,6 @@ DBX 只会在具备足够元数据和 SQL 生成能力的数据库上启用高 大多数网络数据库支持主机、端口、用户名、密码、默认数据库、URL 参数、SSL、连接颜色、SSH 隧道和代理设置。文件型数据库会改为选择本地文件。 -DBX 也可以解析常见连接 URL,例如 MySQL、PostgreSQL、Redis、MongoDB、ClickHouse、SQL Server、Oracle、Elasticsearch、DM、GaussDB、openGauss、TDengine、虚谷 XuguDB 和 Access。 - ### ClickHouse TLS ClickHouse 连接支持 TLS 加密。创建 ClickHouse 连接时,启用 TLS 开关即可通过 HTTPS 而非 HTTP 进行连接。建议对生产环境的 ClickHouse 实例或 ClickHouse Cloud 部署启用此选项。 diff --git a/docs/content/docs/databases.mdx b/docs/content/docs/databases.mdx index ff76fa7bd0..5e52f405b3 100644 --- a/docs/content/docs/databases.mdx +++ b/docs/content/docs/databases.mdx @@ -13,10 +13,10 @@ The connection dialog exposes ready-to-use profiles with default ports and drive | Group | Profiles | Notes | |---|---|---| -| Core SQL engines | MySQL, PostgreSQL, SQLite, DuckDB, ClickHouse, SQL Server, Oracle, Redshift, DM, GaussDB | Available directly in the DBX connection picker | +| Core SQL engines | MySQL, PostgreSQL, SQLite, DuckDB, ClickHouse, SQL Server, Oracle, Redshift, DM, GaussDB, KWDB | Available directly in the DBX connection picker | | Document and key-value engines | Redis, MongoDB, Elasticsearch | Use dedicated browsers or query flows instead of the generic relational grid where appropriate | | MySQL-compatible profiles | MariaDB, TiDB, OceanBase, Doris, SelectDB, StarRocks, GoldenDB, custom MySQL | Reuse MySQL-style connection handling where the engine speaks a compatible protocol | -| PostgreSQL-compatible profiles | openGauss, KingBase, HighGo, Vastbase, CockroachDB, custom PostgreSQL | Reuse PostgreSQL-style connection handling where the engine speaks a compatible protocol | +| PostgreSQL-compatible profiles | KWDB, openGauss, KingBase, HighGo, Vastbase, CockroachDB, custom PostgreSQL | Reuse PostgreSQL-style connection handling where the engine speaks a compatible protocol | | File-based engines | SQLite, DuckDB, Microsoft Access | Choose a local database file instead of host and port | | Agent/JDBC-oriented engines | H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, TDengine, XuguDB, JDBC | Feature coverage depends on the driver path used by that engine | @@ -53,6 +53,7 @@ The connection dialog exposes ready-to-use profiles with default ports and drive | SunDB | 22000 | | TDengine | 6041 | | XuguDB | 5138 | +| KWDB | 26257 | SQLite, DuckDB, Access, and JDBC profiles use port `0` in the saved connection model when a network port is not part of the connection. @@ -64,14 +65,14 @@ DBX intentionally enables advanced workflows only where the app has enough metad | Feature | Supported Types | |---|---| -| Schema-aware tree | PostgreSQL, SQL Server, Oracle, Redshift, DM, GaussDB, KingBase, HighGo, Vastbase, JDBC, H2, Snowflake, Trino, DB2, TDengine, XuguDB | -| ER diagram | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DM, GaussDB, KingBase, HighGo, Vastbase, GoldenDB, Access, H2, DB2 | -| Database search | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DuckDB, ClickHouse, DM, GaussDB, KingBase, HighGo, Vastbase, GoldenDB, Access, H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, TDengine, XuguDB | -| Table import | MySQL, PostgreSQL, SQLite, DuckDB, ClickHouse, SQL Server, Oracle, Doris, StarRocks, Redshift, DM, GaussDB, KingBase, HighGo, Vastbase, GoldenDB, Access | -| Table structure editor | MySQL, PostgreSQL, SQLite, SQL Server | -| Create database | MySQL, PostgreSQL, SQL Server, ClickHouse, Oracle, DM, GaussDB, Doris, StarRocks, Redshift | -| Field lineage | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DM, GaussDB | -| Data transfer | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, ClickHouse, DuckDB, DM, GaussDB | +| Schema-aware tree | PostgreSQL, SQL Server, Oracle, Redshift, DM, GaussDB, KWDB, KingBase, HighGo, Vastbase, JDBC, H2, Snowflake, Trino, DB2, TDengine, XuguDB | +| ER diagram | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DM, GaussDB, KWDB, KingBase, HighGo, Vastbase, GoldenDB, Access, H2, DB2 | +| Database search | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DuckDB, ClickHouse, DM, GaussDB, KWDB, KingBase, HighGo, Vastbase, GoldenDB, Access, H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, TDengine, XuguDB | +| Table import | MySQL, PostgreSQL, SQLite, DuckDB, ClickHouse, SQL Server, Oracle, Doris, StarRocks, Redshift, DM, GaussDB, KWDB, KingBase, HighGo, Vastbase, GoldenDB, Access | +| Table structure editor | MySQL, PostgreSQL, SQLite, SQL Server, KWDB | +| Create database | MySQL, PostgreSQL, SQL Server, ClickHouse, Oracle, DM, GaussDB, KWDB, Doris, StarRocks, Redshift | +| Field lineage | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DM, GaussDB, KWDB | +| Data transfer | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, ClickHouse, DuckDB, DM, GaussDB, KWDB | | SQL file execution unavailable | Redis, MongoDB, Elasticsearch | @@ -82,7 +83,7 @@ If a database can connect but does not appear in a feature row, that feature may Most network databases support host, port, username, password, default database, optional URL parameters, SSL, connection color, SSH tunnel, and proxy settings. File-based engines replace host and port with a file picker. -DBX can also parse common connection URLs for engines such as MySQL, PostgreSQL, Redis, MongoDB, ClickHouse, SQL Server, Oracle, Elasticsearch, DM, GaussDB, openGauss, TDengine, XuguDB, and Access. +DBX can also parse common connection URLs for engines such as MySQL, PostgreSQL, Redis, MongoDB, ClickHouse, SQL Server, Oracle, Elasticsearch, DM, GaussDB, KWDB, openGauss, TDengine, XuguDB, and Access. ### ClickHouse TLS diff --git a/packages/app-tests/connectionUrl.test.ts b/packages/app-tests/connectionUrl.test.ts index 7ac9d7d6d4..56d3d8f442 100644 --- a/packages/app-tests/connectionUrl.test.ts +++ b/packages/app-tests/connectionUrl.test.ts @@ -17,6 +17,21 @@ test("parses postgres connection URLs", () => { }); }); +test("parses KWDB connection URLs", () => { + assert.deepEqual(parseConnectionUrl("kwdb://root:secret@kw.example.com/defaultdb?sslmode=require"), { + dbType: "kwdb", + driverProfile: "kwdb", + driverLabel: "KWDB", + host: "kw.example.com", + port: 26257, + username: "root", + password: "secret", + database: "defaultdb", + urlParams: "sslmode=require", + ssl: true, + }); +}); + test("parses mysql URLs with encoded credentials", () => { const parsed = parseConnectionUrl("mysql://root:p%40ss@127.0.0.1/shop?charset=utf8mb4"); diff --git a/packages/app-tests/connectionUrlPlaceholder.test.ts b/packages/app-tests/connectionUrlPlaceholder.test.ts index 13abcf1213..b7497ac66c 100644 --- a/packages/app-tests/connectionUrlPlaceholder.test.ts +++ b/packages/app-tests/connectionUrlPlaceholder.test.ts @@ -8,6 +8,7 @@ const expected: Record = { starrocks: "mysql://user:password@host:port/database", postgres: "postgresql://user:password@host:port/database", gaussdb: "postgresql://user:password@host:port/database", + kwdb: "postgresql://user:password@host:port/database", redshift: "postgresql://user:password@host:port/database", redis: "redis://:password@host:port/0", sqlite: "sqlite:///absolute/path/to/database.db", @@ -45,6 +46,7 @@ test("mysql-family types share the same placeholder", () => { test("postgres-family types share the same placeholder", () => { const postgres = connectionUrlPlaceholder("postgres"); assert.equal(connectionUrlPlaceholder("gaussdb"), postgres); + assert.equal(connectionUrlPlaceholder("kwdb"), postgres); assert.equal(connectionUrlPlaceholder("redshift"), postgres); }); diff --git a/packages/app-tests/databaseCapabilities.test.ts b/packages/app-tests/databaseCapabilities.test.ts index dc11f39499..f29fba0ef3 100644 --- a/packages/app-tests/databaseCapabilities.test.ts +++ b/packages/app-tests/databaseCapabilities.test.ts @@ -183,6 +183,14 @@ test("uses Navicat-style table editing defaults for updateable SQL table engines requiresTransactionalTableForExistingRows: false, transaction: true, }); + assert.deepEqual(getDatabaseCapability("kwdb").tableData, { + insert: true, + updateRequiresPrimaryKey: false, + deleteRequiresPrimaryKey: false, + keylessRowPredicate: true, + requiresTransactionalTableForExistingRows: false, + transaction: true, + }); }); test("keeps conservative table editing defaults for unknown database types", () => { @@ -210,6 +218,7 @@ test("describes feature support through capability helpers", () => { assert.equal(supportsTableStructureEditing("oracle"), true); assert.equal(supportsTableStructureEditing("dameng"), true); assert.equal(supportsTableStructureEditing("gaussdb"), true); + assert.equal(supportsTableStructureEditing("kwdb"), true); assert.equal(supportsTableStructureEditing("opengauss"), true); assert.equal(supportsTableStructureEditing("redshift"), true); assert.equal(supportsTableStructureEditing("clickhouse"), true); @@ -218,12 +227,15 @@ test("describes feature support through capability helpers", () => { assert.equal(supportsDatabaseCreation("clickhouse"), true); assert.equal(supportsDatabaseCreation("sqlite"), false); assert.equal(supportsFieldLineage("gaussdb"), true); + assert.equal(supportsFieldLineage("kwdb"), true); assert.equal(supportsFieldLineage("trino"), false); assert.equal(supportsTransfer("duckdb"), true); assert.equal(supportsTransfer("hive"), false); assert.equal(supportsDriverManagement("oracle"), true); assert.equal(supportsDriverManagement("mysql"), false); + assert.equal(supportsDriverManagement("kwdb"), false); assert.equal(usesPostgresLikeStructureCopy("gaussdb"), true); + assert.equal(usesPostgresLikeStructureCopy("kwdb"), true); assert.equal(usesPostgresLikeStructureCopy("mysql"), false); assert.equal(supportsObjectBrowser("mysql"), true); assert.equal(supportsObjectBrowser("mongodb"), false); diff --git a/packages/app-tests/tableStructureCapabilities.test.ts b/packages/app-tests/tableStructureCapabilities.test.ts index dbe174ab04..e12c991858 100644 --- a/packages/app-tests/tableStructureCapabilities.test.ts +++ b/packages/app-tests/tableStructureCapabilities.test.ts @@ -14,7 +14,7 @@ test("sqlite-family and duckdb do not support table comments", () => { }); test("postgres-like databases expose safe structure editing capabilities", () => { - for (const dbType of ["postgres", "gaussdb", "opengauss", "highgo", "vastbase", "kingbase"] as const) { + for (const dbType of ["postgres", "gaussdb", "kwdb", "opengauss", "highgo", "vastbase", "kingbase"] as const) { const caps = getTableStructureCapabilities(dbType); assert.equal(caps.dialect, "postgres", `${dbType} should reuse postgres DDL`); assert.equal(caps.createTable, true, `${dbType} should create tables`); diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 945516f6b1..bb0d2de349 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -46,7 +46,7 @@ function formatQueryToolResult(result: QueryResult, title?: string) { } export const DBX_CONNECTION_TYPE_DESCRIPTION = - "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, gbase, h2, snowflake, trino, hive, db2, informix, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, xugu, jdbc, access"; + "Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, gbase, h2, snowflake, trino, hive, db2, informix, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, xugu, jdbc, access"; export function createDbxMcpServer(backend: Backend, options: { isWebMode?: boolean } = {}): McpServer { const isWebMode = options.isWebMode ?? !!process.env.DBX_WEB_URL; @@ -181,7 +181,7 @@ export function createDbxMcpServer(backend: Backend, options: { isWebMode?: bool const existing = await backend.findConnection(name); if (existing) return text(`Connection "${name}" already exists.`); const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]); - const DEFAULT_PORTS: Record = { rqlite: 4001, tdengine: 6041, xugu: 5138 }; + const DEFAULT_PORTS: Record = { kwdb: 26257, rqlite: 4001, tdengine: 6041, xugu: 5138 }; const resolvedPort = port ?? DEFAULT_PORTS[db_type] ?? (FILE_BASED_TYPES.has(db_type) ? 0 : undefined); if (resolvedPort === undefined) return text("Port is required for this database type."); const config = await backend.addConnection({ diff --git a/packages/node-core/src/diagnostics.ts b/packages/node-core/src/diagnostics.ts index eb6e7cd5c5..70871f4e6a 100644 --- a/packages/node-core/src/diagnostics.ts +++ b/packages/node-core/src/diagnostics.ts @@ -11,6 +11,7 @@ export const DIRECT_QUERY_TYPES = [ "sqlite", "rqlite", "gaussdb", + "kwdb", "opengauss", ] as const; diff --git a/src-tauri/src/commands/connection.rs b/src-tauri/src/commands/connection.rs index 7d80ba7779..b0bc008870 100644 --- a/src-tauri/src/commands/connection.rs +++ b/src-tauri/src/commands/connection.rs @@ -290,15 +290,17 @@ pub async fn test_connection(state: State<'_, Arc>, config: Connection Err(e) => Err(e), } } - DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb | DatabaseType::OpenGauss => { - match db::postgres::connect(&url, connect_timeout).await { - Ok(pool) => { - pool.close(); - Ok("Connection successful".to_string()) - } - Err(e) => Err(e), + DatabaseType::Postgres + | DatabaseType::Redshift + | DatabaseType::Gaussdb + | DatabaseType::Kwdb + | DatabaseType::OpenGauss => match db::postgres::connect(&url, connect_timeout).await { + Ok(pool) => { + pool.close(); + Ok("Connection successful".to_string()) } - } + Err(e) => Err(e), + }, DatabaseType::Sqlite => { let extensions = db::sqlite::sqlite_extension_specs_from_url_params(config.url_params.as_deref()) .into_iter() @@ -457,9 +459,11 @@ pub async fn connect_db(state: State<'_, Arc>, config: ConnectionConfi connect_bare_metadata_pool(&db_config, &host, port, connect_timeout).await?, MysqlMode::Bare, ), - DatabaseType::Postgres | DatabaseType::Redshift | DatabaseType::Gaussdb | DatabaseType::OpenGauss => { - PoolKind::Postgres(db::postgres::connect(&url, connect_timeout).await?) - } + DatabaseType::Postgres + | DatabaseType::Redshift + | DatabaseType::Gaussdb + | DatabaseType::Kwdb + | DatabaseType::OpenGauss => PoolKind::Postgres(db::postgres::connect(&url, connect_timeout).await?), DatabaseType::Sqlite => { let extensions = db::sqlite::sqlite_extension_specs_from_url_params(db_config.url_params.as_deref()) .into_iter()