From e3af13ed7ec21a30c82e6cdda23b20a42ed62312 Mon Sep 17 00:00:00 2001 From: "Crane.z" <1481445951@qq.com> Date: Thu, 17 Sep 2026 18:05:11 +0800 Subject: [PATCH 1/2] fix(clickhouse): correct database context handling --- .../clickhouse/ClickhouseActuator.java | 2 +- .../clickhouse/ClickhouseConnectionManager.java | 11 ++++++++++- .../jumpserver/chen/web/ai/SqlAgentToolService.java | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActuator.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActuator.java index 777b2c91..e569bc64 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActuator.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActuator.java @@ -28,7 +28,7 @@ public String getCurrentSchema() throws SQLException { @Override public List getSchemas() throws SQLException { - var result = this.execute(SQL.of("show databases")); + var result = this.execute(SQL.of("SELECT name FROM system.databases ORDER BY name")); return result.getData().stream().map(row -> (String) row.get(0)).toList(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseConnectionManager.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseConnectionManager.java index d1558a4a..44a79dc1 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseConnectionManager.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseConnectionManager.java @@ -1,5 +1,6 @@ package org.jumpserver.chen.modules.clickhouse; +import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.Datasource; import org.jumpserver.chen.framework.datasource.base.BaseConnectionManager; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; @@ -48,9 +49,17 @@ public String getDisplayJDBCUrl() { return this.getConnectInfo().toDisplayJDBCUrl(jdbcUrlTemplate); } + @Override + public String getDatabaseContextKey() { + // ClickHouse exposes databases through schema nodes, like MySQL/MariaDB. + return "schema"; + } @Override public String getJDBCUrl(String database) { - return this.jdbcUrl; + if (StringUtils.isBlank(database)) { + return this.jdbcUrl; + } + return this.getConnectInfo().toJDBCUrl(jdbcUrlTemplate, database); } } diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java b/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java index 8b933a34..7234125d 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java @@ -637,8 +637,8 @@ private static RelationScope relationScope(AgentRequestContext context, String r StringUtils.defaultIfBlank(requestedSchema, context.schema()) )); String dialect = StringUtils.defaultString(context.dialect()).toLowerCase(Locale.ROOT); - if ("mysql".equals(dialect) || "mariadb".equals(dialect)) { - // MySQL/MariaDB providers query INFORMATION_SCHEMA by scope.schema(), + if ("mysql".equals(dialect) || "mariadb".equals(dialect) || "clickhouse".equals(dialect)) { + // MySQL/MariaDB/ClickHouse providers query metadata by scope.schema(), // matching ResourceBrowser.resolveScope(catalog=null, schema=database). return new RelationScope(null, schema != null ? schema : database); } From a0738037a69b8de85ecce7f5bebaf8179868ffd6 Mon Sep 17 00:00:00 2001 From: "Crane.z" <1481445951@qq.com> Date: Thu, 17 Sep 2026 18:32:50 +0800 Subject: [PATCH 2/2] fix(metadata): support table statistics --- .../chen/web/entity/TableMetadata.java | 7 ++++++ .../web/service/TableMetadataService.java | 22 +++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadata.java index e30e2b4c..ec70e9c9 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadata.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/TableMetadata.java @@ -15,6 +15,7 @@ public record TableMetadata( List foreignKeys, List indexes, List constraints, + Statistics statistics, String ddl ) { public record Capabilities( @@ -23,6 +24,9 @@ public record Capabilities( boolean foreignKeys, boolean indexes, boolean constraints, + boolean statistics, + boolean estimatedRows, + boolean totalSizeBytes, boolean ddl ) { } @@ -43,6 +47,9 @@ public record Column( public record PrimaryKey(String name, List columns) { } + public record Statistics(Long estimatedRows, Long totalSizeBytes) { + } + public record ForeignKey( String name, List columns, diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/TableMetadataService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/TableMetadataService.java index dd0c668a..808b1532 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/service/TableMetadataService.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/TableMetadataService.java @@ -12,6 +12,7 @@ import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; import org.jumpserver.chen.framework.datasource.metadata.PrimaryKeyMetadata; import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.web.entity.TableMetadata; @@ -29,7 +30,7 @@ public class TableMetadataService { private static final Set DEFAULT_SECTIONS = Set.of("columns", "primaryKey"); private static final Set AUDITED_PROPERTY_SECTIONS = Set.of( - "foreignKeys", "indexes", "constraints", "ddl" + "foreignKeys", "indexes", "constraints", "statistics", "ddl" ); public TableMetadata getTableMetadata(TableMetadataRequest request) { @@ -60,7 +61,11 @@ static boolean shouldAuditPropertyQuery(Set sections, boolean force) { TableMetadata load(MetadataCatalog catalog, ObjectRef ref, Set sections, boolean force) throws SQLException { if (force) { - catalog.invalidate(ref); + if (sections.contains("statistics")) { + catalog.invalidate(new RelationScope(ref.catalog(), ref.schema())); + } else { + catalog.invalidate(ref); + } } return load(catalog, ref, sections); } @@ -88,12 +93,19 @@ TableMetadata load(MetadataCatalog catalog, ObjectRef ref, Set sections) var constraints = sections.contains("constraints") ? catalog.listConstraints(List.of(ref)).stream().map(this::toConstraint).toList() : List.of(); + var statistics = sections.contains("statistics") + ? catalog.listStatistics(new RelationScope(ref.catalog(), ref.schema())).stream() + .filter(item -> ref.equals(item.ref())) + .findFirst() + .map(item -> new TableMetadata.Statistics(item.estimatedRows(), item.totalSizeBytes())) + .orElse(null) + : null; var ddl = sections.contains("ddl") ? catalog.getTableDefinition(ref) : null; return new TableMetadata( ref.catalog(), ref.schema(), ref.name(), ref.kind().code(), toCapabilities(catalog.capabilities()), sections, - columns, primaryKey, foreignKeys, indexes, constraints, ddl + columns, primaryKey, foreignKeys, indexes, constraints, statistics, ddl ); } @@ -136,7 +148,8 @@ private TableMetadata.Constraint toConstraint(ConstraintMetadata constraint) { private TableMetadata.Capabilities toCapabilities(MetadataCapabilities capabilities) { return new TableMetadata.Capabilities( capabilities.columns(), capabilities.primaryKeys(), capabilities.foreignKeys(), - capabilities.indexes(), capabilities.constraints(), capabilities.tableDefinitions() + capabilities.indexes(), capabilities.constraints(), capabilities.statistics(), + capabilities.tableRows(), capabilities.tableSize(), capabilities.tableDefinitions() ); } @@ -154,6 +167,7 @@ static Set normalizeSections(Set requested) { case "foreignkeys", "foreignkey", "fk" -> "foreignKeys"; case "indexes", "indices" -> "indexes"; case "constraints" -> "constraints"; + case "statistics", "stats" -> "statistics"; case "ddl" -> "ddl"; default -> throw new IllegalArgumentException(MessageUtils.getOrDefault( "UnknownTableMetadataSection", "Unknown table metadata section: %s", section));