diff --git a/README.md b/README.md
index 40042a291a..2125a2e0d6 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@
### 70+ Databases, One Tool
-MySQL, PostgreSQL, SQLite, Cloudflare D1, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, Qdrant, Milvus, Weaviate, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KWDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Manticore Search, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. Agent/JDBC-oriented profiles extend DBX to H2, Snowflake, Trino, PrestoSQL, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, JDBCX, and custom JDBC connections. New native and agent-driven drivers also cover Databricks, SAP HANA, Teradata, Vertica, Firebird, Exasol, YashanDB, GBase 8a/8s, Databend, RQLite, Turso, InfluxDB, QuestDB, IoTDB, etcd, ZooKeeper, Nacos, IRIS, and more. Message queue admin is also available for Pulsar, Kafka, and RocketMQ. All in a single ~20 MB app. No bundled Chromium.
+MySQL, PostgreSQL, SQLite, Cloudflare D1, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, Qdrant, Milvus, Weaviate, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KWDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Manticore Search, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, UXDB, and more. Agent/JDBC-oriented profiles extend DBX to H2, Snowflake, Trino, PrestoSQL, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, JDBCX, and custom JDBC connections. New native and agent-driven drivers also cover Databricks, SAP HANA, Teradata, Vertica, Firebird, Exasol, YashanDB, GBase 8a/8s, Databend, RQLite, Turso, InfluxDB, QuestDB, IoTDB, etcd, ZooKeeper, Nacos, IRIS, and more. Message queue admin is also available for Pulsar, Kafka, and RocketMQ. All in a single ~20 MB app. No bundled Chromium.
### Query Editor
@@ -428,7 +428,7 @@ DBX is 20 MB with no runtime dependencies for its native database features (no s
What databases are supported?
-MySQL, PostgreSQL, SQLite, Cloudflare D1, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, Qdrant, Milvus, Weaviate, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KWDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Manticore Search, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. Agent/JDBC-oriented profiles extend support to H2, Snowflake, Trino, PrestoSQL, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, JDBCX, Databricks, SAP HANA, Teradata, Vertica, Firebird, Exasol, YashanDB, GBase 8a/8s, Databend, RQLite, Turso, InfluxDB, QuestDB, IoTDB, etcd, ZooKeeper, Nacos, IRIS, and custom JDBC connections. Message queue admin (Pulsar, Kafka, RocketMQ) is also supported.
+MySQL, PostgreSQL, SQLite, Cloudflare D1, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, Qdrant, Milvus, Weaviate, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KWDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Manticore Search, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, UXDB, and more. Agent/JDBC-oriented profiles extend support to H2, Snowflake, Trino, PrestoSQL, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, JDBCX, Databricks, SAP HANA, Teradata, Vertica, Firebird, Exasol, YashanDB, GBase 8a/8s, Databend, RQLite, Turso, InfluxDB, QuestDB, IoTDB, etcd, ZooKeeper, Nacos, IRIS, and custom JDBC connections. Message queue admin (Pulsar, Kafka, RocketMQ) is also supported.
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 9bfe249912..4d26483bf8 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -90,7 +90,7 @@
### 70+ 种数据库,一个工具搞定
-MySQL、PostgreSQL、SQLite、Cloudflare D1、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KWDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Manticore Search、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等数据库都能直接连接。Agent/JDBC 方向的配置还可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、JDBCX 和自定义 JDBC。新增的原生与 Agent 驱动还覆盖了 Databricks、SAP HANA、Teradata、Vertica、Firebird、Exasol、崖山 YashanDB、GBase、Databend、RQLite、Turso、InfluxDB、QuestDB、IoTDB、etcd、IRIS 等。全部装进约 20 MB 的应用里,不内嵌 Chromium。
+MySQL、PostgreSQL、SQLite、Cloudflare D1、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KWDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Manticore Search、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo、UXDB 等数据库都能直接连接。Agent/JDBC 方向的配置还可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、JDBCX 和自定义 JDBC。新增的原生与 Agent 驱动还覆盖了 Databricks、SAP HANA、Teradata、Vertica、Firebird、Exasol、崖山 YashanDB、GBase、Databend、RQLite、Turso、InfluxDB、QuestDB、IoTDB、etcd、IRIS 等。全部装进约 20 MB 的应用里,不内嵌 Chromium。
### 查询编辑器
@@ -426,7 +426,7 @@ DBX 仅 20 MB,无需运行时依赖(无需 Java、无需 Python)。AI 和
支持哪些数据库?
-MySQL、PostgreSQL、SQLite、Cloudflare D1、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、Qdrant、Milvus、Weaviate、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KWDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Manticore Search、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等。JDBC 方向配置可扩展到 H2、Snowflake、Trino、PrestoSQL、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、JDBCX、Databricks、SAP HANA、Teradata、Vertica、Firebird、Exasol、崖山 YashanDB、GBase 8a/8s、Databend、RQLite、Turso、InfluxDB、QuestDB、IoTDB、etcd、ZooKeeper、Nacos、IRIS 及自定义 JDBC 连接,并支持消息队列管理(Pulsar、Kafka、RocketMQ)。
+MySQL、PostgreSQL、SQLite、Cloudflare D1、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、Qdrant、Milvus、Weaviate、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KWDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Manticore Search、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo、UXDB 等。JDBC 方向配置可扩展到 H2、Snowflake、Trino、PrestoSQL、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、JDBCX、Databricks、SAP HANA、Teradata、Vertica、Firebird、Exasol、崖山 YashanDB、GBase 8a/8s、Databend、RQLite、Turso、InfluxDB、QuestDB、IoTDB、etcd、ZooKeeper、Nacos、IRIS 及自定义 JDBC 连接,并支持消息队列管理(Pulsar、Kafka、RocketMQ)。
diff --git a/agents/.gitignore b/agents/.gitignore
index e1afb2aa4f..5a2037d7fa 100644
--- a/agents/.gitignore
+++ b/agents/.gitignore
@@ -1,5 +1,6 @@
**/libs/*.jar
!drivers/oscar/libs/oscarJDBC8.jar
+!drivers/uxdb/libs/uxdbjdbc-2.1.2.3p.jre8.jar
gradle.properties
build/
*/bin/
diff --git a/agents/README.md b/agents/README.md
index 88c71db4ba..f7723bc306 100644
--- a/agents/README.md
+++ b/agents/README.md
@@ -14,6 +14,7 @@ Each agent runs as a standalone process and communicates with DBX via stdin/stdo
| dameng | 达梦 DM8 | DM JDBC |
| kingbase | 人大金仓 KingbaseES | gokb Go native agent |
| vastbase | Vastbase | Vastbase JDBC |
+| uxdb | UXDB | UXDB JDBC |
| goldendb | GoldenDB | MySQL Connector/J |
| databend | Databend | Databend JDBC |
| databricks | Databricks SQL | Databricks JDBC |
diff --git a/agents/README.zh-CN.md b/agents/README.zh-CN.md
index 9c669c7c3f..2309dd3fce 100644
--- a/agents/README.zh-CN.md
+++ b/agents/README.zh-CN.md
@@ -14,6 +14,7 @@ DBX 的 Agent 驱动 —— 通过 JDBC 和原生数据库驱动支持各种数
| dameng | 达梦 DM8 | DM JDBC |
| kingbase | 人大金仓 KingbaseES | gokb Go 原生 agent |
| vastbase | Vastbase | Vastbase JDBC |
+| uxdb | 优炫 UXDB | UXDB JDBC |
| goldendb | GoldenDB | MySQL Connector/J |
| databend | Databend | Databend JDBC |
| databricks | Databricks SQL | Databricks JDBC |
diff --git a/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgent.java b/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgent.java
index 220a254492..7c74578264 100644
--- a/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgent.java
+++ b/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgent.java
@@ -136,7 +136,9 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
public List listDatabases() {
return unchecked(() -> {
List result = new ArrayList<>();
- try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement("SELECT datname FROM pg_catalog.pg_database WHERE datistemplate = false ORDER BY datname");
+ String sql = "SELECT datname FROM " + profile.catalogRelation("database") +
+ " WHERE datistemplate = false ORDER BY datname";
+ try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
result.add(new DatabaseInfo(rs.getString("datname")));
@@ -152,10 +154,10 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
List result = new ArrayList<>();
try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement(
"SELECT n.nspname AS schema_name " +
- "FROM pg_catalog.pg_namespace n " +
- "WHERE n.nspname NOT IN ('pg_catalog','information_schema','pg_toast') " +
- "AND n.nspname NOT LIKE 'pg_toast_temp_%' " +
- "AND n.nspname NOT LIKE 'pg_temp_%' " +
+ "FROM " + profile.catalogRelation("namespace") + " n " +
+ "WHERE n.nspname NOT IN ('" + profile.getCatalogSchema() + "','information_schema','" + profile.getToastSchema() + "') " +
+ "AND n.nspname NOT LIKE '" + profile.getToastTemporarySchemaPrefix() + "%' " +
+ "AND n.nspname NOT LIKE '" + profile.getTemporarySchemaPrefix() + "%' " +
"ORDER BY n.nspname"
); ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
@@ -179,9 +181,9 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
"WHEN 'm' THEN 'MATERIALIZED VIEW' " +
"WHEN 'f' THEN 'FOREIGN TABLE' " +
"ELSE 'TABLE' END AS table_type, " +
- "obj_description(c.oid) AS table_comment " +
- "FROM pg_catalog.pg_class c " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
+ profile.catalogBuiltinFunction("obj_description") + "(c.oid) AS table_comment " +
+ "FROM " + profile.catalogRelation("class") + " c " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
"WHERE n.nspname = ? AND c.relkind IN ('r','p','v','m','f') " +
"ORDER BY c.relname"
)) {
@@ -207,8 +209,8 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement(
"SELECT p.proname AS routine_name, " +
"CASE p.prokind WHEN 'p' THEN 'PROCEDURE' ELSE 'FUNCTION' END AS routine_type " +
- "FROM pg_catalog.pg_proc p " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace " +
+ "FROM " + profile.catalogRelation("proc") + " p " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = p.pronamespace " +
"WHERE n.nspname = ? AND p.prokind IN ('p','f') " +
"ORDER BY p.proname"
)) {
@@ -221,8 +223,8 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
}
try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement(
"SELECT c.relname AS sequence_name, 'SEQUENCE' AS object_type " +
- "FROM pg_catalog.pg_class c " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
+ "FROM " + profile.catalogRelation("class") + " c " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
"WHERE n.nspname = ? AND c.relkind = 'S' " +
"ORDER BY c.relname"
)) {
@@ -286,9 +288,9 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
public String getTableComment(String schema, String table) {
return unchecked(() -> {
try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement(
- "SELECT obj_description(c.oid) AS table_comment " +
- "FROM pg_catalog.pg_class c " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
+ "SELECT " + profile.catalogBuiltinFunction("obj_description") + "(c.oid) AS table_comment " +
+ "FROM " + profile.catalogRelation("class") + " c " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
"WHERE n.nspname = ? AND c.relname = ? AND c.relkind IN ('r','m','f','p') " +
"LIMIT 1"
)) {
@@ -308,10 +310,11 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
protected List listCheckConstraints(String schema, String table) {
return unchecked(() -> {
List result = new ArrayList<>();
- String sql = "SELECT co.conname AS constraint_name, pg_catalog.pg_get_constraintdef(co.oid, true) AS constraint_definition " +
- "FROM pg_catalog.pg_constraint co " +
- "JOIN pg_catalog.pg_class c ON c.oid = co.conrelid " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
+ String sql = "SELECT co.conname AS constraint_name, " +
+ profile.catalogPrefixedFunction("get_constraintdef") + "(co.oid, true) AS constraint_definition " +
+ "FROM " + profile.catalogRelation("constraint") + " co " +
+ "JOIN " + profile.catalogRelation("class") + " c ON c.oid = co.conrelid " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
"WHERE co.contype = 'c' AND n.nspname = ? AND c.relname = ? " +
"ORDER BY co.conname";
try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement(sql)) {
@@ -336,15 +339,18 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
String upperType = objectType.toUpperCase();
String sql;
if ("VIEW".equals(upperType) || "MATERIALIZED VIEW".equals(upperType)) {
- sql = "SELECT pg_catalog.pg_get_viewdef(pg_catalog.to_regclass(?), true)";
+ sql = "SELECT " + profile.catalogPrefixedFunction("get_viewdef") + "(" +
+ profile.catalogBuiltinFunction("to_regclass") + "(?), true)";
} else if ("FUNCTION".equals(upperType)) {
- sql = "SELECT pg_catalog.pg_get_functiondef(p.oid)\n" +
- "FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" +
+ sql = "SELECT " + profile.catalogPrefixedFunction("get_functiondef") + "(p.oid)\n" +
+ "FROM " + profile.catalogRelation("proc") + " p JOIN " +
+ profile.catalogRelation("namespace") + " n ON n.oid = p.pronamespace\n" +
"WHERE n.nspname = ? AND p.proname = ? AND p.prokind = 'f'\n" +
"ORDER BY p.oid LIMIT 1";
} else if ("PROCEDURE".equals(upperType)) {
- sql = "SELECT pg_catalog.pg_get_functiondef(p.oid)\n" +
- "FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" +
+ sql = "SELECT " + profile.catalogPrefixedFunction("get_functiondef") + "(p.oid)\n" +
+ "FROM " + profile.catalogRelation("proc") + " p JOIN " +
+ profile.catalogRelation("namespace") + " n ON n.oid = p.pronamespace\n" +
"WHERE n.nspname = ? AND p.proname = ? AND p.prokind = 'p'\n" +
"ORDER BY p.oid LIMIT 1";
} else {
@@ -378,21 +384,21 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
Set primaryKeys = primaryKeys(schema, table);
List result = new ArrayList<>();
String sql = "SELECT a.attname AS column_name, " +
- "format_type(a.atttypid, a.atttypmod) AS data_type, " +
+ profile.catalogBuiltinFunction("format_type") + "(a.atttypid, a.atttypmod) AS data_type, " +
"NOT a.attnotnull AS is_nullable, " +
- "pg_get_expr(ad.adbin, ad.adrelid) AS column_default, " +
- "col_description(a.attrelid, a.attnum) AS column_comment, " +
+ profile.catalogPrefixedFunction("get_expr") + "(ad.adbin, ad.adrelid) AS column_default, " +
+ profile.catalogBuiltinFunction("col_description") + "(a.attrelid, a.attnum) AS column_comment, " +
"CASE WHEN t.typname = 'numeric' AND a.atttypmod > 0 " +
"THEN ((a.atttypmod - 4) >> 16) & 65535 ELSE NULL END AS numeric_precision, " +
"CASE WHEN t.typname = 'numeric' AND a.atttypmod > 0 " +
"THEN (a.atttypmod - 4) & 65535 ELSE NULL END AS numeric_scale, " +
"CASE WHEN t.typname IN ('varchar', 'bpchar') AND a.atttypmod > 0 " +
"THEN a.atttypmod - 4 ELSE NULL END AS character_maximum_length " +
- "FROM pg_catalog.pg_attribute a " +
- "JOIN pg_catalog.pg_type t ON t.oid = a.atttypid " +
- "JOIN pg_catalog.pg_class c ON c.oid = a.attrelid " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
- "LEFT JOIN pg_catalog.pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum " +
+ "FROM " + profile.catalogRelation("attribute") + " a " +
+ "JOIN " + profile.catalogRelation("type") + " t ON t.oid = a.atttypid " +
+ "JOIN " + profile.catalogRelation("class") + " c ON c.oid = a.attrelid " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
+ "LEFT JOIN " + profile.catalogRelation("attrdef") + " ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum " +
"WHERE n.nspname = ? AND c.relname = ? " +
"AND a.attnum > 0 AND NOT a.attisdropped " +
"ORDER BY a.attnum";
@@ -425,11 +431,11 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
return unchecked(() -> {
Set primaryKeys = new LinkedHashSet<>();
String sql = "SELECT a.attname AS column_name " +
- "FROM pg_catalog.pg_constraint co " +
- "JOIN pg_catalog.pg_class c ON c.oid = co.conrelid " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
+ "FROM " + profile.catalogRelation("constraint") + " co " +
+ "JOIN " + profile.catalogRelation("class") + " c ON c.oid = co.conrelid " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
"JOIN LATERAL (SELECT unnest(co.conkey) AS attnum, generate_series(1, array_length(co.conkey, 1)) AS ord) AS pk_cols ON true " +
- "JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = pk_cols.attnum " +
+ "JOIN " + profile.catalogRelation("attribute") + " a ON a.attrelid = c.oid AND a.attnum = pk_cols.attnum " +
"WHERE co.contype = 'p' " +
"AND n.nspname = ? " +
"AND c.relname = ? " +
@@ -454,13 +460,13 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
String sql = "SELECT i.relname AS index_name, am.amname AS index_type, " +
"ix.indisunique AS is_unique, ix.indisprimary AS is_primary, " +
"array_agg(a.attname ORDER BY k.n) AS columns " +
- "FROM pg_catalog.pg_index ix " +
- "JOIN pg_catalog.pg_class t ON t.oid = ix.indrelid " +
- "JOIN pg_catalog.pg_class i ON i.oid = ix.indexrelid " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace " +
- "JOIN pg_catalog.pg_am am ON am.oid = i.relam " +
+ "FROM " + profile.catalogRelation("index") + " ix " +
+ "JOIN " + profile.catalogRelation("class") + " t ON t.oid = ix.indrelid " +
+ "JOIN " + profile.catalogRelation("class") + " i ON i.oid = ix.indexrelid " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = t.relnamespace " +
+ "JOIN " + profile.catalogRelation("am") + " am ON am.oid = i.relam " +
"JOIN LATERAL (SELECT unnest(ix.indkey) AS attnum, generate_series(1, array_length(ix.indkey, 1)) AS n) AS k ON true " +
- "JOIN pg_catalog.pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum " +
+ "JOIN " + profile.catalogRelation("attribute") + " a ON a.attrelid = t.oid AND a.attnum = k.attnum " +
"WHERE n.nspname = ? AND t.relname = ? " +
"GROUP BY i.relname, am.amname, ix.indisunique, ix.indisprimary " +
"ORDER BY i.relname";
@@ -499,14 +505,14 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
"a.attname AS column_name, " +
"rc.relname AS ref_table, " +
"ra.attname AS ref_column " +
- "FROM pg_catalog.pg_constraint co " +
- "JOIN pg_catalog.pg_class c ON c.oid = co.conrelid " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
- "JOIN pg_catalog.pg_class rc ON rc.oid = co.confrelid " +
+ "FROM " + profile.catalogRelation("constraint") + " co " +
+ "JOIN " + profile.catalogRelation("class") + " c ON c.oid = co.conrelid " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
+ "JOIN " + profile.catalogRelation("class") + " rc ON rc.oid = co.confrelid " +
"JOIN LATERAL (SELECT unnest(co.conkey) AS attnum, generate_series(1, array_length(co.conkey, 1)) AS ord) AS fk ON true " +
- "JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = fk.attnum " +
+ "JOIN " + profile.catalogRelation("attribute") + " a ON a.attrelid = c.oid AND a.attnum = fk.attnum " +
"JOIN LATERAL (SELECT unnest(co.confkey) AS attnum, generate_series(1, array_length(co.confkey, 1)) AS ord) AS pk ON pk.ord = fk.ord " +
- "JOIN pg_catalog.pg_attribute ra ON ra.attrelid = rc.oid AND ra.attnum = pk.attnum " +
+ "JOIN " + profile.catalogRelation("attribute") + " ra ON ra.attrelid = rc.oid AND ra.attnum = pk.attnum " +
"WHERE co.contype = 'f' " +
"AND n.nspname = ? " +
"AND c.relname = ? " +
@@ -541,9 +547,9 @@ public abstract class PostgresLikeAgent extends AbstractJdbcAgent {
"CASE WHEN (tg.tgtype & 32) <> 0 THEN 'TRUNCATE,' ELSE '' END" +
")) AS event_manipulation, " +
"CASE WHEN (tg.tgtype & 2) <> 0 THEN 'BEFORE' ELSE 'AFTER' END AS action_timing " +
- "FROM pg_catalog.pg_trigger tg " +
- "JOIN pg_catalog.pg_class c ON c.oid = tg.tgrelid " +
- "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
+ "FROM " + profile.catalogRelation("trigger") + " tg " +
+ "JOIN " + profile.catalogRelation("class") + " c ON c.oid = tg.tgrelid " +
+ "JOIN " + profile.catalogRelation("namespace") + " n ON n.oid = c.relnamespace " +
"WHERE n.nspname = ? AND c.relname = ? AND NOT tg.tgisinternal " +
"ORDER BY tg.tgname";
try (java.sql.PreparedStatement stmt = requireConnection().prepareStatement(sql)) {
diff --git a/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgentProfile.java b/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgentProfile.java
index f4ba2a8e97..afea7880cb 100644
--- a/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgentProfile.java
+++ b/agents/common/src/main/java/com/dbx/agent/PostgresLikeAgentProfile.java
@@ -3,10 +3,38 @@ package com.dbx.agent;
public final class PostgresLikeAgentProfile {
private final String driverClass;
private final String urlTemplate;
+ private final int defaultPort;
+ private final String catalogSchema;
+ private final String catalogPrefix;
public PostgresLikeAgentProfile(String driverClass, String urlTemplate) {
+ this(driverClass, urlTemplate, 0, "pg_catalog", "pg_");
+ }
+
+ public PostgresLikeAgentProfile(
+ String driverClass,
+ String urlTemplate,
+ String catalogSchema,
+ String catalogPrefix
+ ) {
+ this(driverClass, urlTemplate, 0, catalogSchema, catalogPrefix);
+ }
+
+ public PostgresLikeAgentProfile(
+ String driverClass,
+ String urlTemplate,
+ int defaultPort,
+ String catalogSchema,
+ String catalogPrefix
+ ) {
this.driverClass = driverClass;
this.urlTemplate = urlTemplate;
+ this.defaultPort = defaultPort;
+ // PostgreSQL derivatives may preserve catalog semantics while renaming every
+ // relation and helper function. Centralize that mapping instead of copying
+ // the full metadata implementation for each compatible database.
+ this.catalogSchema = catalogSchema;
+ this.catalogPrefix = catalogPrefix;
}
public String getDriverClass() {
@@ -17,11 +45,39 @@ public final class PostgresLikeAgentProfile {
return urlTemplate;
}
+ public String getCatalogSchema() {
+ return catalogSchema;
+ }
+
+ public String getToastSchema() {
+ return catalogPrefix + "toast";
+ }
+
+ public String getTemporarySchemaPrefix() {
+ return catalogPrefix + "temp_";
+ }
+
+ public String getToastTemporarySchemaPrefix() {
+ return catalogPrefix + "toast_temp_";
+ }
+
+ public String catalogRelation(String name) {
+ return catalogSchema + "." + catalogPrefix + name;
+ }
+
+ public String catalogPrefixedFunction(String name) {
+ return catalogSchema + "." + catalogPrefix + name;
+ }
+
+ public String catalogBuiltinFunction(String name) {
+ return catalogSchema + "." + name;
+ }
+
public String buildUrl(ConnectParams params) {
return new JdbcAgentProfile(
driverClass,
urlTemplate,
- 0,
+ defaultPort,
false,
java.util.Collections.emptySet(),
java.util.Arrays.asList("TABLE", "VIEW", "MATERIALIZED VIEW", "SYSTEM TABLE", "SYSTEM VIEW")
diff --git a/agents/common/src/test/java/com/dbx/agent/PostgresLikeAgentTest.java b/agents/common/src/test/java/com/dbx/agent/PostgresLikeAgentTest.java
index 0960ed6bdc..7779b6fb86 100644
--- a/agents/common/src/test/java/com/dbx/agent/PostgresLikeAgentTest.java
+++ b/agents/common/src/test/java/com/dbx/agent/PostgresLikeAgentTest.java
@@ -54,6 +54,40 @@ class PostgresLikeAgentTest {
assertFalse(sql.contains(" key."), sql);
}
+ @Test
+ void metadataQueriesSupportRenamedPostgresCatalogs() {
+ TestPostgresLikeAgent agent = new TestPostgresLikeAgent(new PostgresLikeAgentProfile(
+ PostgresLikeAgentTest.class.getName(),
+ "jdbc:uxdb://{host}:{port}/{database}",
+ 52025,
+ "ux_catalog",
+ "ux_"
+ ));
+ agent.connect(new ConnectParams());
+
+ agent.listDatabases();
+ agent.listSchemas();
+ agent.listTables("app");
+ agent.listObjects("app");
+ agent.getObjectSource("app", "refresh_orders", "FUNCTION");
+ agent.getColumns("app", "orders");
+ agent.listCheckConstraintsForTest("app", "orders");
+ agent.listIndexes("app", "orders");
+ agent.listForeignKeys("app", "orders");
+ agent.listTriggers("app", "orders");
+
+ String sql = String.join("\n", MetadataSqlFake.statements);
+
+ assertTrue(sql.contains("ux_catalog.ux_database"), sql);
+ assertTrue(sql.contains("ux_catalog.ux_namespace"), sql);
+ assertTrue(sql.contains("ux_catalog.ux_class"), sql);
+ assertTrue(sql.contains("ux_catalog.ux_proc"), sql);
+ assertTrue(sql.contains("ux_catalog.ux_get_constraintdef"), sql);
+ assertTrue(sql.contains("ux_catalog.ux_get_functiondef"), sql);
+ assertTrue(sql.contains("ux_catalog.ux_get_expr"), sql);
+ assertFalse(sql.contains("pg_catalog"), sql);
+ }
+
@Test
void mapsPgCatalogBooleanNullableValues() {
TestPostgresLikeAgent agent = new TestPostgresLikeAgent(preparedConnection(
@@ -156,14 +190,22 @@ class PostgresLikeAgentTest {
private final Connection connection;
private TestPostgresLikeAgent() {
- this(null);
+ this((Connection) null);
}
private TestPostgresLikeAgent(Connection connection) {
- super(new PostgresLikeAgentProfile(
+ this(new PostgresLikeAgentProfile(
PostgresLikeAgentTest.class.getName(),
"jdbc:test://{host}:{port}/{database}"
- ));
+ ), connection);
+ }
+
+ private TestPostgresLikeAgent(PostgresLikeAgentProfile profile) {
+ this(profile, null);
+ }
+
+ private TestPostgresLikeAgent(PostgresLikeAgentProfile profile, Connection connection) {
+ super(profile);
this.connection = connection;
}
diff --git a/agents/drivers/uxdb/build.gradle b/agents/drivers/uxdb/build.gradle
new file mode 100644
index 0000000000..1bae00878c
--- /dev/null
+++ b/agents/drivers/uxdb/build.gradle
@@ -0,0 +1,9 @@
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+}
+
+tasks.named('shadowJar') {
+ manifest {
+ attributes('Agent-Label': 'UXDB', 'Main-Class': 'com.dbx.agent.uxdb.UxdbAgent')
+ }
+}
diff --git a/agents/drivers/uxdb/libs/uxdbjdbc-2.1.2.3p.jre8.jar b/agents/drivers/uxdb/libs/uxdbjdbc-2.1.2.3p.jre8.jar
new file mode 100644
index 0000000000..ac4e9b185d
Binary files /dev/null and b/agents/drivers/uxdb/libs/uxdbjdbc-2.1.2.3p.jre8.jar differ
diff --git a/agents/drivers/uxdb/src/main/java/com/dbx/agent/uxdb/UxdbAgent.java b/agents/drivers/uxdb/src/main/java/com/dbx/agent/uxdb/UxdbAgent.java
new file mode 100644
index 0000000000..47477a58a5
--- /dev/null
+++ b/agents/drivers/uxdb/src/main/java/com/dbx/agent/uxdb/UxdbAgent.java
@@ -0,0 +1,23 @@
+package com.dbx.agent.uxdb;
+
+import com.dbx.agent.MultiSessionJsonRpcServer;
+import com.dbx.agent.PostgresLikeAgent;
+import com.dbx.agent.PostgresLikeAgentProfile;
+
+public final class UxdbAgent extends PostgresLikeAgent {
+ public static final PostgresLikeAgentProfile UXDB_PROFILE = new PostgresLikeAgentProfile(
+ "com.uxsino.uxdb.Driver",
+ "jdbc:uxdb://{host}:{port}/{database}",
+ 52025,
+ "ux_catalog",
+ "ux_"
+ );
+
+ public UxdbAgent() {
+ super(UXDB_PROFILE);
+ }
+
+ public static void main(String[] args) {
+ new MultiSessionJsonRpcServer(UxdbAgent::new).run();
+ }
+}
diff --git a/agents/drivers/uxdb/src/test/java/com/dbx/agent/uxdb/UxdbAgentTest.java b/agents/drivers/uxdb/src/test/java/com/dbx/agent/uxdb/UxdbAgentTest.java
new file mode 100644
index 0000000000..3cbc3d6212
--- /dev/null
+++ b/agents/drivers/uxdb/src/test/java/com/dbx/agent/uxdb/UxdbAgentTest.java
@@ -0,0 +1,25 @@
+package com.dbx.agent.uxdb;
+
+import com.dbx.agent.ConnectParams;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class UxdbAgentTest {
+ @Test
+ void profileUsesOfficialJdbcSettings() {
+ assertEquals("com.uxsino.uxdb.Driver", UxdbAgent.UXDB_PROFILE.getDriverClass());
+ assertEquals("ux_catalog", UxdbAgent.UXDB_PROFILE.getCatalogSchema());
+ assertEquals("ux_catalog.ux_database", UxdbAgent.UXDB_PROFILE.catalogRelation("database"));
+ assertEquals(
+ "jdbc:uxdb://localhost:52025/uxdb",
+ UxdbAgent.UXDB_PROFILE.buildUrl(new ConnectParams("localhost", 0, "uxdb", "uxdb", "", "", "", false))
+ );
+ }
+
+ @Test
+ void usesPostgresStyleSchemaSearchPath() {
+ assertEquals("SET search_path TO \"public\"", new UxdbAgent().setSchemaSQL("public"));
+ assertEquals("RESET search_path", new UxdbAgent().resetSchemaSQL());
+ }
+}
diff --git a/agents/settings.gradle b/agents/settings.gradle
index 2337a9a9b8..b06bec2f09 100644
--- a/agents/settings.gradle
+++ b/agents/settings.gradle
@@ -5,7 +5,7 @@ def driverModules = [
'access', 'dameng', 'vastbase', 'goldendb', 'databend', 'databricks', 'saphana',
'teradata', 'vertica', 'firebird', 'exasol', 'oceanbase-oracle', 'gbase8a', 'gbase8s',
'bigquery', 'kylin', 'sundb', 'h2', 'h2-legacy', 'snowflake', 'trino', 'hive', 'spark',
- 'db2', 'informix', 'neo4j', 'cassandra', 'mongodb', 'highgo', 'tdengine', 'yashandb', 'oscar',
+ 'db2', 'informix', 'neo4j', 'cassandra', 'mongodb', 'highgo', 'uxdb', 'tdengine', 'yashandb', 'oscar',
'iris', 'iotdb', 'etcd', 'zookeeper', 'kafka', 'rocketmq', 'rabbitmq', 'sqlserver-legacy'
]
diff --git a/agents/versions.json b/agents/versions.json
index eafe57361d..62b6b7a180 100644
--- a/agents/versions.json
+++ b/agents/versions.json
@@ -31,6 +31,7 @@
"vertica": "0.1.28",
"mongodb": "0.1.35",
"highgo": "0.1.31",
+ "uxdb": "0.1.0",
"yashandb": "0.1.29",
"oscar": "0.1.8",
"xugu": "0.1.23",
diff --git a/apps/desktop/public/icons/database/uxdb-dark.svg b/apps/desktop/public/icons/database/uxdb-dark.svg
new file mode 100644
index 0000000000..3811322e51
--- /dev/null
+++ b/apps/desktop/public/icons/database/uxdb-dark.svg
@@ -0,0 +1,149 @@
+
+
+
diff --git a/apps/desktop/public/icons/database/uxdb.svg b/apps/desktop/public/icons/database/uxdb.svg
new file mode 100644
index 0000000000..68ff41450e
--- /dev/null
+++ b/apps/desktop/public/icons/database/uxdb.svg
@@ -0,0 +1,149 @@
+
+
+
diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue
index 8e2dbf25af..5231d1488f 100644
--- a/apps/desktop/src/components/connection/ConnectionDialog.vue
+++ b/apps/desktop/src/components/connection/ConnectionDialog.vue
@@ -817,6 +817,7 @@ const driverProfiles: Record<
questdb: { type: "questdb", port: 8812, user: "questdb", label: "QuestDB", icon: "questdb" },
kingbase: { type: "kingbase", port: 54321, user: "system", label: "KingBase", icon: "kingbase" },
highgo: { type: "highgo", port: 5866, user: "highgo", label: "瀚高 HighGo", icon: "highgo" },
+ uxdb: { type: "uxdb", port: 52025, user: "uxdb", label: "优炫 UXDB", icon: "uxdb" },
yashandb: { type: "yashandb", port: 1688, user: "sys", label: "崖山 YashanDB", icon: "yashandb" },
vastbase: { type: "vastbase", port: 5432, user: "vastbase", label: "Vastbase", icon: "vastbase" },
doris: { type: "mysql", port: 9030, user: "root", label: "Doris", icon: "doris", urlParams: "" },
@@ -2077,6 +2078,7 @@ function defaultDatabaseForProfile() {
if (form.value.db_type === "databend") return "default";
if (selectedType.value === "cockroachdb") return "defaultdb";
if (form.value.db_type === "highgo") return "highgo";
+ if (form.value.db_type === "uxdb") return "uxdb";
if (form.value.db_type === "yashandb") return "yasdb";
if (form.value.db_type === "postgres" || form.value.db_type === "vastbase") return "postgres";
if (form.value.db_type === "sqlserver") return "master";
@@ -2155,6 +2157,7 @@ const iconTypeMap: Record = {
questdb: "questdb",
kingbase: "kingbase",
highgo: "highgo",
+ uxdb: "uxdb",
yashandb: "yashandb",
vastbase: "vastbase",
doris: "doris",
diff --git a/apps/desktop/src/components/icons/DatabaseIcon.vue b/apps/desktop/src/components/icons/DatabaseIcon.vue
index 0afaffded3..ff3eedbfe8 100644
--- a/apps/desktop/src/components/icons/DatabaseIcon.vue
+++ b/apps/desktop/src/components/icons/DatabaseIcon.vue
@@ -1,11 +1,13 @@