From bdfcab34782ed8e7a37b3e87f2975fc223177d68 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Mon, 1 Jun 2026 19:15:37 +0800 Subject: [PATCH 1/9] feat: add sematic in agent --- .../agent/datasource/SchemaReader.java | 136 ++++++++ .../agent/datasource/TableRelationInfo.java | 28 ++ .../agent/tools/GetTableSchemaTool.java | 83 ++--- .../malonetalk/agent/tools/GetTablesTool.java | 61 ++-- .../tools/response/ColumnPromptResponse.java | 26 ++ .../tools/response/TablePromptResponse.java | 26 ++ .../tools/response/TableRelationResponse.java | 29 ++ .../github/malonetalk/common/ToolError.java | 20 ++ .../github/malonetalk/common/ToolResult.java | 29 ++ .../convertor/ColumnSemanticConverter.java | 35 +++ .../LogicalTableRelationConverter.java | 61 ++++ .../convertor/TableSemanticConverter.java | 33 ++ .../semantic/TableSchemaSemanticPrompt.java | 27 ++ .../malonetalk/service/DatasourceService.java | 2 + .../service/DatasourceServiceImpl.java | 19 ++ .../semantic/SemanticMergeService.java | 296 ++++++++++++++++++ .../column/ColumnSemanticServiceImpl.java | 20 +- .../relation/RelationSemanticServiceImpl.java | 37 +-- .../semantic/table/TableSemanticService.java | 3 - .../table/TableSemanticServiceImpl.java | 28 +- 20 files changed, 846 insertions(+), 153 deletions(-) create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/TableRelationInfo.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/ColumnPromptResponse.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TablePromptResponse.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TableRelationResponse.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/common/ToolError.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/common/ToolResult.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/convertor/ColumnSemanticConverter.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/convertor/TableSemanticConverter.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/SchemaReader.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/SchemaReader.java index 2654bfe..d778fa3 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/SchemaReader.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/SchemaReader.java @@ -18,13 +18,16 @@ package io.github.malonetalk.agent.datasource; import io.github.malonetalk.entity.Datasource; +import io.github.malonetalk.entity.TableInfo; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -50,6 +53,105 @@ public List getTableSchema(Datasource datasource, String tableName) } } + public List getTables(Datasource datasource) { + javax.sql.DataSource ds = dynamicDataSourceManager.getOrCreateDataSource(datasource); + try (Connection conn = ds.getConnection()) { + DatabaseMetaData metaData = conn.getMetaData(); + List tables = new ArrayList<>(); + try (ResultSet rs = + metaData.getTables( + conn.getCatalog(), conn.getSchema(), null, new String[] {"TABLE"})) { + while (rs.next()) { + String tableName = rs.getString("TABLE_NAME"); + if (tableName == null || tableName.isBlank()) { + continue; + } + TableInfo tableInfo = new TableInfo(); + tableInfo.setTableName(tableName); + tableInfo.setTableDescription( + normalizeBlankToNull(rs.getString("REMARKS"))); + tableInfo.setDatasourceId(datasource.getId()); + tables.add(tableInfo); + } + } + return tables; + } catch (SQLException e) { + log.error( + "Failed to read tables for datasource {}: {}", + datasource.getId(), + e.getMessage(), + e); + throw new SchemaReadException( + "Failed to read tables for datasource " + + datasource.getId() + + ": " + + e.getMessage(), + e); + } + } + + public List getImportedRelations( + Datasource datasource, String tableName) { + javax.sql.DataSource ds = dynamicDataSourceManager.getOrCreateDataSource(datasource); + try (Connection conn = ds.getConnection()) { + DatabaseMetaData metaData = conn.getMetaData(); + Map relationMap = new LinkedHashMap<>(); + try (ResultSet rs = + metaData.getImportedKeys( + conn.getCatalog(), conn.getSchema(), tableName)) { + while (rs.next()) { + String sourceTable = rs.getString("FKTABLE_NAME"); + String sourceColumn = rs.getString("FKCOLUMN_NAME"); + String targetTable = rs.getString("PKTABLE_NAME"); + String targetColumn = rs.getString("PKCOLUMN_NAME"); + if (anyBlank(sourceTable, sourceColumn, targetTable, targetColumn)) { + continue; + } + String key = + buildRelationKey( + sourceTable, + sourceColumn, + targetTable, + targetColumn, + normalizeBlankToNull(rs.getString("FK_NAME"))); + TableRelationInfo existing = relationMap.get(key); + if (existing != null) { + List mergedSource = + mergeList(existing.sourceColumnNames(), sourceColumn); + List mergedTarget = + mergeList(existing.targetColumnNames(), targetColumn); + relationMap.put( + key, + new TableRelationInfo( + sourceTable, mergedSource, targetTable, mergedTarget, + "foreign_key", null)); + } else { + relationMap.put( + key, + new TableRelationInfo( + sourceTable, List.of(sourceColumn), + targetTable, List.of(targetColumn), + "foreign_key", null)); + } + } + } + return List.copyOf(relationMap.values()); + } catch (SQLException e) { + log.error( + "Failed to read imported relations for table {} in datasource {}: {}", + tableName, + datasource.getId(), + e.getMessage(), + e); + throw new SchemaReadException( + "Failed to read imported relations for table " + + tableName + + ": " + + e.getMessage(), + e); + } + } + private Set getPrimaryKeys(Connection conn, String tableName) throws SQLException { Set pkColumns = new HashSet<>(); DatabaseMetaData metaData = conn.getMetaData(); @@ -96,6 +198,40 @@ private List getColumns(Connection conn, String tableName, Set mergeList(List existing, String newValue) { + List merged = new ArrayList<>(existing); + merged.add(newValue); + return merged; + } + public static class SchemaReadException extends RuntimeException { public SchemaReadException(String message, Throwable cause) { super(message, cause); diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/TableRelationInfo.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/TableRelationInfo.java new file mode 100644 index 0000000..1aede31 --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/TableRelationInfo.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.agent.datasource; + +import java.util.List; + +public record TableRelationInfo( + String sourceTableName, + List sourceColumnNames, + String targetTableName, + List targetColumnNames, + String relationType, + String description) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java index d7c7032..3928b06 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java @@ -19,69 +19,50 @@ import io.agentscope.core.tool.Tool; import io.agentscope.core.tool.ToolParam; -import io.github.malonetalk.agent.datasource.ColumnInfo; -import io.github.malonetalk.agent.datasource.SchemaReader; -import io.github.malonetalk.agent.datasource.SchemaReader.SchemaReadException; -import io.github.malonetalk.entity.Datasource; -import io.github.malonetalk.enums.Status; -import io.github.malonetalk.service.DatasourceService; -import java.util.List; -import lombok.AllArgsConstructor; +import io.github.malonetalk.common.ToolResult; +import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.semantic.TableSchemaSemanticPrompt; +import io.github.malonetalk.service.semantic.SemanticMergeService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component -@AllArgsConstructor +@RequiredArgsConstructor public class GetTableSchemaTool implements MarkAgentTool { - private final DatasourceService dataSourceService; - private final SchemaReader schemaReader; + private final SemanticMergeService semanticMergeService; @Tool( name = "get_table_schema", description = - "Get the schema information of the specified table, including column name, data" - + " type, whether it is primary key, whether it allows null, default value" - + " and column comments. This tool should be called to understand the table" - + " structure before generating SQL.") - public String getTableSchema( - @ToolParam(name = "table_name", description = "The table name to query schema for") - String tableName) { - List activeDataSources = - dataSourceService.findByStatus(Status.ACTIVE.getCode()); - - if (activeDataSources.isEmpty()) { - return "No active datasource available, cannot get table schema."; - } - - if (activeDataSources.size() > 1) { - log.warn( - "Found {} active data sources, using the first one.", activeDataSources.size()); - } - - Datasource datasource = activeDataSources.get(0); - + "获取指定表的语义 Schema 信息,包含表元数据和分页可见列信息。" + + "语义描述优先于物理元数据作为兜底。" + + "表关系通过 get_tables 工具获取,不通过本工具返回。" + + "在生成 SQL 之前应调用此工具了解表结构。") + public ToolResult getTableSchema( + @ToolParam(name = "table_name", description = "要查询 Schema 的表名") + String tableName, + @ToolParam(name = "column_page", description = "可选列页码,默认为1") + Integer columnPage, + @ToolParam( + name = "column_page_size", + description = "可选列每页大小,默认20,最大100") + Integer columnPageSize) { try { - List columns = schemaReader.getTableSchema(datasource, tableName); - return formatSchema(tableName, columns); - } catch (SchemaReadException e) { - return "Failed to get table schema: " + e.getMessage(); + int resolvedPage = PageResponse.resolvePage(columnPage); + int resolvedPageSize = PageResponse.resolvePageSize(columnPageSize); + return ToolResult.success( + semanticMergeService.getTableSchema( + tableName, resolvedPage, resolvedPageSize)); + } catch (IllegalStateException e) { + return ToolResult.error("数据源解析失败", e.getMessage()); + } catch (IllegalArgumentException e) { + return ToolResult.error("参数错误", e.getMessage()); + } catch (RuntimeException e) { + log.error("获取表 {} 的 Schema 失败: {}", tableName, e.getMessage(), e); + return ToolResult.error("获取 Schema 失败", e.getMessage()); } } - - private String formatSchema(String tableName, List columns) { - if (columns.isEmpty()) { - return "Table " + tableName + " does not exist or has no column information."; - } - - StringBuilder sb = new StringBuilder(); - sb.append("Schema of table ").append(tableName).append(":\n"); - - for (ColumnInfo col : columns) { - sb.append(" - ").append(col.toString()).append("\n"); - } - - return sb.toString(); - } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java index c98eb91..f19a6a1 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java @@ -18,42 +18,47 @@ package io.github.malonetalk.agent.tools; import io.agentscope.core.tool.Tool; -import io.github.malonetalk.entity.Datasource; -import io.github.malonetalk.entity.TableInfo; -import io.github.malonetalk.enums.Status; -import io.github.malonetalk.service.DatasourceService; -import io.github.malonetalk.service.semantic.table.TableSemanticService; -import java.util.Collections; -import java.util.List; -import lombok.AllArgsConstructor; +import io.agentscope.core.tool.ToolParam; +import io.github.malonetalk.agent.tools.response.TablePromptResponse; +import io.github.malonetalk.common.ToolResult; +import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.service.semantic.SemanticMergeService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component -@AllArgsConstructor +@RequiredArgsConstructor public class GetTablesTool implements MarkAgentTool { - private final DatasourceService dataSourceService; - private final TableSemanticService tableSemanticService; + private final SemanticMergeService semanticMergeService; - @Tool(name = "get_tables", description = "获取数据库中的表信息,包括表名和表描述") - public List getTables() { - List activeDataSources = - dataSourceService.findByStatus(Status.ACTIVE.getCode()); - - if (activeDataSources.isEmpty()) { - return Collections.emptyList(); - } - - if (activeDataSources.size() > 1) { - log.warn( - "Found {} active data sources, using the first one. This may cause data" - + " inconsistency.", - activeDataSources.size()); + @Tool( + name = "get_tables", + description = + "获取可见的表信息,包含表名、领域、描述以及该表与其他表的逻辑关系。" + + "返回 success/data/error 包装结构,data 包含分页的表项。") + public ToolResult> getTables( + @ToolParam(name = "page", description = "可选页码,默认为1") + Integer page, + @ToolParam( + name = "page_size", + description = "可选每页大小,默认20,最大100") + Integer pageSize) { + try { + int resolvedPage = PageResponse.resolvePage(page); + int resolvedPageSize = PageResponse.resolvePageSize(pageSize); + return ToolResult.success( + semanticMergeService.getVisibleTablePromptPage( + resolvedPage, resolvedPageSize)); + } catch (IllegalStateException e) { + return ToolResult.error("数据源解析失败", e.getMessage()); + } catch (IllegalArgumentException e) { + return ToolResult.error("参数错误", e.getMessage()); + } catch (RuntimeException e) { + log.error("获取可见表失败: {}", e.getMessage(), e); + return ToolResult.error("获取表失败", e.getMessage()); } - - Datasource dataSource = activeDataSources.get(0); - return tableSemanticService.listTableInfosByDatasourceId(dataSource.getId()); } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/ColumnPromptResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/ColumnPromptResponse.java new file mode 100644 index 0000000..42dcd86 --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/ColumnPromptResponse.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.agent.tools.response; + +public record ColumnPromptResponse( + String name, + String type, + Boolean primaryKey, + Boolean nullable, + String defaultValue, + String description) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TablePromptResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TablePromptResponse.java new file mode 100644 index 0000000..e4fdc0a --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TablePromptResponse.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.agent.tools.response; + +import java.util.List; + +public record TablePromptResponse( + String name, + String domain, + String description, + List relations) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TableRelationResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TableRelationResponse.java new file mode 100644 index 0000000..1da72c7 --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/response/TableRelationResponse.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.agent.tools.response; + +import java.util.List; + +public record TableRelationResponse( + String relationType, + String source, + String sourceTableName, + List sourceColumnNames, + String targetTableName, + List targetColumnNames, + String description) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/common/ToolError.java b/data-agent-backend/src/main/java/io/github/malonetalk/common/ToolError.java new file mode 100644 index 0000000..8514999 --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/common/ToolError.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.common; + +public record ToolError(String code, String message) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/common/ToolResult.java b/data-agent-backend/src/main/java/io/github/malonetalk/common/ToolResult.java new file mode 100644 index 0000000..91edc3b --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/common/ToolResult.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.common; + +public record ToolResult(boolean success, T data, ToolError error) { + + public static ToolResult success(T data) { + return new ToolResult<>(true, data, null); + } + + public static ToolResult error(String code, String message) { + return new ToolResult<>(false, null, new ToolError(code, message)); + } +} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/convertor/ColumnSemanticConverter.java b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/ColumnSemanticConverter.java new file mode 100644 index 0000000..f0b698a --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/ColumnSemanticConverter.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.convertor; + +import io.github.malonetalk.dto.semantic.ColumnSemanticResponse; +import io.github.malonetalk.entity.ColumnInfo; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface ColumnSemanticConverter { + + @Mapping(target = "physicalColumnDescription", expression = "java(null)") + @Mapping(target = "typeName", expression = "java(null)") + @Mapping(target = "primaryKey", expression = "java(null)") + @Mapping(target = "hasPhysicalColumn", constant = "true") + @Mapping(target = "effective", expression = "java(Boolean.TRUE.equals(columnInfo.getIsVisible()))") + @Mapping(target = "invalidReason", expression = "java(null)") + ColumnSemanticResponse toResponse(ColumnInfo columnInfo); +} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java new file mode 100644 index 0000000..e41e79d --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.convertor; + +import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; +import io.github.malonetalk.entity.LogicalTableRelation; +import io.github.malonetalk.service.semantic.relation.LogicalTableRelationHelper; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class LogicalTableRelationConverter { + + private final LogicalTableRelationHelper logicalTableRelationHelper; + + public LogicalTableRelationResponse toResponse(LogicalTableRelation relation) { + List sourceColumns = + logicalTableRelationHelper.fromJson( + relation.getSourceColumnNamesJson(), "sourceColumnNames"); + List targetColumns = + logicalTableRelationHelper.fromJson( + relation.getTargetColumnNamesJson(), "targetColumnNames"); + return new LogicalTableRelationResponse( + relation.getId(), + logicalTableRelationHelper.buildRelationKey( + relation.getSourceTableName(), + sourceColumns, + relation.getTargetTableName(), + targetColumns), + relation.getDatasourceId(), + LogicalTableRelationHelper.RELATION_SOURCE_LOGICAL, + relation.getSourceTableName(), + sourceColumns, + relation.getTargetTableName(), + targetColumns, + relation.getRelationType(), + relation.getDescription(), + relation.getIsEnabled(), + relation.getIsEnabled(), + null, + relation.getCreateTime(), + relation.getUpdateTime()); + } +} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/convertor/TableSemanticConverter.java b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/TableSemanticConverter.java new file mode 100644 index 0000000..d696f0c --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/TableSemanticConverter.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.convertor; + +import io.github.malonetalk.dto.semantic.TableSemanticResponse; +import io.github.malonetalk.entity.TableInfo; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface TableSemanticConverter { + + @Mapping(target = "physicalTableDescription", expression = "java(null)") + @Mapping(target = "hasPhysicalTable", constant = "true") + @Mapping(target = "effective", expression = "java(Boolean.TRUE.equals(tableInfo.getIsVisible()))") + @Mapping(target = "invalidReason", expression = "java(null)") + TableSemanticResponse toResponse(TableInfo tableInfo); +} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java new file mode 100644 index 0000000..8f04607 --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.dto.semantic; + +import io.github.malonetalk.agent.tools.response.ColumnPromptResponse; +import io.github.malonetalk.dto.pagination.PageResponse; + +public record TableSchemaSemanticPrompt( + String name, + String domain, + String description, + PageResponse columns) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceService.java index 03e1b02..8fe3b6d 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceService.java @@ -37,4 +37,6 @@ public interface DatasourceService { List findByType(String type); boolean updateStatus(Integer id, String status); + + Datasource requireActiveDatasource(); } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceServiceImpl.java index b3b6b87..ca75a63 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/DatasourceServiceImpl.java @@ -18,12 +18,15 @@ package io.github.malonetalk.service; import io.github.malonetalk.entity.Datasource; +import io.github.malonetalk.enums.Status; import io.github.malonetalk.mapper.DatasourceMapper; import java.time.LocalDateTime; import java.util.List; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +@Slf4j @Service @AllArgsConstructor public class DatasourceServiceImpl implements DatasourceService { @@ -72,4 +75,20 @@ public List findByType(String type) { public boolean updateStatus(Integer id, String status) { return dataSourceMapper.updateStatus(id, status) > 0; } + + @Override + public Datasource requireActiveDatasource() { + List activeDataSources = + dataSourceMapper.selectByStatus(Status.ACTIVE.getCode()); + if (activeDataSources.isEmpty()) { + throw new IllegalStateException("没有活跃的数据源,请先激活一个数据源。"); + } + if (activeDataSources.size() > 1) { + List activeIds = activeDataSources.stream().map(Datasource::getId).toList(); + String message = "存在多个活跃数据源 " + activeIds + ",请仅保留一个活跃数据源。"; + log.error("发现 {} 个活跃数据源,拒绝隐式选择。activeIds={}", activeDataSources.size(), activeIds); + throw new IllegalStateException(message); + } + return activeDataSources.get(0); + } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java new file mode 100644 index 0000000..4fe18a3 --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.service.semantic; + +import io.github.malonetalk.agent.datasource.ColumnInfo; +import io.github.malonetalk.agent.datasource.SchemaReader; +import io.github.malonetalk.agent.tools.response.ColumnPromptResponse; +import io.github.malonetalk.agent.tools.response.TablePromptResponse; +import io.github.malonetalk.agent.tools.response.TableRelationResponse; +import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.semantic.TableSchemaSemanticPrompt; +import io.github.malonetalk.entity.Datasource; +import io.github.malonetalk.entity.LogicalTableRelation; +import io.github.malonetalk.entity.TableInfo; +import io.github.malonetalk.mapper.ColumnSemanticInfoMapper; +import io.github.malonetalk.mapper.LogicalTableRelationMapper; +import io.github.malonetalk.mapper.TableInfoMapper; +import io.github.malonetalk.service.DatasourceService; +import io.github.malonetalk.service.semantic.relation.LogicalTableRelationHelper; +import io.github.malonetalk.utils.SemanticUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SemanticMergeService { + + private final DatasourceService datasourceService; + private final SchemaReader schemaReader; + private final TableInfoMapper tableInfoMapper; + private final ColumnSemanticInfoMapper columnSemanticInfoMapper; + private final LogicalTableRelationMapper logicalTableRelationMapper; + private final LogicalTableRelationHelper logicalTableRelationHelper; + + public PageResponse getVisibleTablePromptPage(int page, int pageSize) { + Datasource datasource = datasourceService.requireActiveDatasource(); + List physicalTables = schemaReader.getTables(datasource); + List semanticTables = + tableInfoMapper.selectByDatasourceId(datasource.getId()); + Map semanticByKey = + toTableKeyMap(semanticTables); + + List visibleTables = new ArrayList<>(); + for (TableInfo physicalTable : physicalTables) { + String key = normalizeKey(physicalTable.getTableName()); + TableInfo semanticTable = semanticByKey.get(key); + if (!isTableVisible(semanticTable)) { + continue; + } + String description = + resolveSemanticFirst( + semanticTable != null ? semanticTable.getTableDescription() : null, + physicalTable.getTableDescription()); + String domain = + resolveSemanticFirst( + semanticTable != null ? semanticTable.getDomain() : null, null); + visibleTables.add( + new TablePromptResponse( + physicalTable.getTableName(), + domain, + description, + Collections.emptyList())); + } + + // 为每个可见表附加关系 + Map> logicalRelationsBySource = + buildLogicalRelationsBySource(datasource.getId()); + for (int i = 0; i < visibleTables.size(); i++) { + TablePromptResponse item = visibleTables.get(i); + List relations = + resolveVisibleRelations( + item.name(), logicalRelationsBySource, datasource.getId()); + visibleTables.set( + i, + new TablePromptResponse( + item.name(), + item.domain(), + item.description(), + relations)); + } + + // 内存分页 + return paginateInMemory(visibleTables, page, pageSize); + } + + public TableSchemaSemanticPrompt getTableSchema(String tableName, int page, int pageSize) { + Datasource datasource = datasourceService.requireActiveDatasource(); + String normalizedTableName = SemanticUtils.requireName(tableName, "tableName"); + + // 校验表存在且可见 + TableInfo semanticTable = + tableInfoMapper.selectByDatasourceIdAndTableName( + datasource.getId(), normalizedTableName); + if (!isTableVisible(semanticTable)) { + throw new IllegalArgumentException("表 " + normalizedTableName + " 不存在或不可见。"); + } + + // 读物理列 + List physicalColumns; + try { + physicalColumns = schemaReader.getTableSchema(datasource, normalizedTableName); + } catch (SchemaReader.SchemaReadException e) { + throw new IllegalArgumentException( + "无法读取表 " + normalizedTableName + " 的 Schema: " + e.getMessage(), e); + } + + // 读语义列 + if (physicalColumns.isEmpty()) { + throw new IllegalArgumentException( + "表 " + normalizedTableName + " 不存在或没有可读列。"); + } + + List semanticColumns = + columnSemanticInfoMapper.selectByDatasourceIdAndTableName( + datasource.getId(), normalizedTableName); + Map semanticByKey = + toColumnKeyMap(semanticColumns); + + // 合并列 + List visibleColumns = new ArrayList<>(); + for (ColumnInfo physicalColumn : physicalColumns) { + String key = normalizeKey(physicalColumn.columnName()); + io.github.malonetalk.entity.ColumnInfo semanticColumn = semanticByKey.get(key); + if (!isColumnVisible(semanticColumn)) { + continue; + } + visibleColumns.add(mapColumnPrompt(physicalColumn, semanticColumn)); + } + + String description = + resolveSemanticFirst( + semanticTable != null ? semanticTable.getTableDescription() : null, null); + String domain = + resolveSemanticFirst( + semanticTable != null ? semanticTable.getDomain() : null, null); + + PageResponse columnPage = + paginateInMemory(visibleColumns, page, pageSize); + return new TableSchemaSemanticPrompt( + normalizedTableName, domain, description, columnPage); + } + + private List resolveVisibleRelations( + String sourceTableName, + Map> logicalRelationsBySource, + Integer datasourceId) { + List logicalRelations = + logicalRelationsBySource.getOrDefault( + normalizeKey(sourceTableName), Collections.emptyList()); + if (logicalRelations.isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (LogicalTableRelation relation : logicalRelations) { + if (!Boolean.TRUE.equals(relation.getIsEnabled())) { + continue; + } + List sourceColumns; + List targetColumns; + try { + sourceColumns = + logicalTableRelationHelper.fromJson( + relation.getSourceColumnNamesJson(), "sourceColumnNames"); + targetColumns = + logicalTableRelationHelper.fromJson( + relation.getTargetColumnNamesJson(), "targetColumnNames"); + } catch (IllegalArgumentException e) { + log.warn( + "跳过无效的逻辑关系 id={}: {}", relation.getId(), e.getMessage()); + continue; + } + result.add( + new TableRelationResponse( + relation.getRelationType(), + LogicalTableRelationHelper.RELATION_SOURCE_LOGICAL, + relation.getSourceTableName(), + sourceColumns, + relation.getTargetTableName(), + targetColumns, + relation.getDescription())); + } + return result; + } + + private ColumnPromptResponse mapColumnPrompt( + ColumnInfo physicalColumn, + io.github.malonetalk.entity.ColumnInfo semanticColumn) { + String description = + resolveSemanticFirst( + semanticColumn != null ? semanticColumn.getColumnDescription() : null, + physicalColumn.remarks()); + StringBuilder typeText = new StringBuilder(physicalColumn.typeName()); + if (physicalColumn.columnSize() > 0) { + typeText.append("(").append(physicalColumn.columnSize()).append(")"); + } + return new ColumnPromptResponse( + physicalColumn.columnName(), + typeText.toString(), + physicalColumn.primaryKey(), + physicalColumn.nullable(), + SemanticUtils.normalizeBlankToNull(physicalColumn.defaultValue()), + description); + } + + private boolean isTableVisible(TableInfo semanticTable) { + if (semanticTable != null) { + return Boolean.TRUE.equals(semanticTable.getIsVisible()); + } + return true; + } + + private boolean isColumnVisible(io.github.malonetalk.entity.ColumnInfo semanticColumn) { + if (semanticColumn != null) { + return Boolean.TRUE.equals(semanticColumn.getIsVisible()); + } + return true; + } + + private Map> buildLogicalRelationsBySource( + Integer datasourceId) { + List allRelations = + logicalTableRelationMapper.selectByDatasourceId(datasourceId); + Map> result = new HashMap<>(); + for (LogicalTableRelation relation : allRelations) { + String key = normalizeKey(relation.getSourceTableName()); + result.computeIfAbsent(key, k -> new ArrayList<>()).add(relation); + } + return result; + } + + private Map toTableKeyMap(List tables) { + Map map = new LinkedHashMap<>(); + for (TableInfo table : tables) { + map.put(normalizeKey(table.getTableName()), table); + } + return map; + } + + private Map toColumnKeyMap( + List columns) { + Map map = new LinkedHashMap<>(); + for (io.github.malonetalk.entity.ColumnInfo column : columns) { + map.put(normalizeKey(column.getColumnName()), column); + } + return map; + } + + private PageResponse paginateInMemory( + List items, int page, int pageSize) { + if (items.isEmpty()) { + return PageResponse.empty(page, pageSize); + } + int start = (page - 1) * pageSize; + if (start >= items.size()) { + return PageResponse.empty(page, pageSize); + } + int end = Math.min(start + pageSize, items.size()); + List pageItems = items.subList(start, end); + return PageResponse.of(pageItems, items.size(), page, pageSize); + } + + private String normalizeKey(String value) { + return logicalTableRelationHelper.normalizeIdentifierKey(value); + } + + private String resolveSemanticFirst(String semanticValue, String physicalValue) { + String resolved = SemanticUtils.normalizeBlankToNull(semanticValue); + if (resolved != null) { + return resolved; + } + return SemanticUtils.normalizeBlankToNull(physicalValue); + } +} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java index 295f1e2..478794e 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java @@ -20,6 +20,7 @@ import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import io.github.malonetalk.common.SemanticConstants; +import io.github.malonetalk.convertor.ColumnSemanticConverter; import io.github.malonetalk.dto.pagination.PageResponse; import io.github.malonetalk.dto.semantic.ColumnSemanticPageQuery; import io.github.malonetalk.dto.semantic.ColumnSemanticResponse; @@ -42,6 +43,7 @@ public class ColumnSemanticServiceImpl implements ColumnSemanticService { private final DatasourceService datasourceService; private final ColumnSemanticInfoMapper columnSemanticInfoMapper; + private final ColumnSemanticConverter columnSemanticConverter; @Override public PageResponse getColumnPage(ColumnSemanticPageQuery query) { @@ -67,7 +69,8 @@ public PageResponse getColumnPage(ColumnSemanticPageQuer SemanticUtils.normalizeBlankToNull(query.keyword()), query.sortOrder()), sortDescending); - List responses = page.stream().map(this::mapResponse).toList(); + List responses = + page.stream().map(columnSemanticConverter::toResponse).toList(); return PageResponse.of(responses, page.getTotal(), pageNumber, pageSize); } @@ -160,21 +163,6 @@ private void requireDatasource(Integer datasourceId) { } } - private ColumnSemanticResponse mapResponse(ColumnInfo columnInfo) { - return new ColumnSemanticResponse( - columnInfo.getId(), - columnInfo.getColumnName(), - null, - columnInfo.getColumnDescription(), - null, - null, - columnInfo.getIsVisible(), - true, - Boolean.TRUE.equals(columnInfo.getIsVisible()), - null, - columnInfo.getUpdateTime()); - } - private String normalizeKey(String value) { return value.trim().toLowerCase(Locale.ROOT); } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java index 4ebaf41..07ff629 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java @@ -20,6 +20,7 @@ import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import io.github.malonetalk.common.SemanticConstants; +import io.github.malonetalk.convertor.LogicalTableRelationConverter; import io.github.malonetalk.dto.pagination.PageResponse; import io.github.malonetalk.dto.semantic.BindLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; @@ -43,6 +44,7 @@ public class RelationSemanticServiceImpl implements RelationSemanticService { private final DatasourceService datasourceService; private final LogicalTableRelationMapper logicalTableRelationMapper; private final LogicalTableRelationHelper logicalTableRelationHelper; + private final LogicalTableRelationConverter logicalTableRelationConverter; @Override public PageResponse getRelationPage( @@ -71,7 +73,8 @@ public PageResponse getRelationPage( if (page.getTotal() == 0L) { return PageResponse.empty(pageNumber, pageSize); } - List items = page.stream().map(this::mapResponse).toList(); + List items = + page.stream().map(logicalTableRelationConverter::toResponse).toList(); return PageResponse.of(items, page.getTotal(), pageNumber, pageSize); } @@ -87,7 +90,7 @@ public LogicalTableRelationResponse createRelationSemantic( relation.getSourceColumnSignature(), null); logicalTableRelationMapper.insert(relation); - return mapResponse(relation); + return logicalTableRelationConverter.toResponse(relation); } @Override @@ -105,7 +108,7 @@ public LogicalTableRelationResponse updateRelationSemantic( existing.getId()); existing.setUpdateTime(LocalDateTime.now()); logicalTableRelationMapper.update(existing); - return mapResponse(existing); + return logicalTableRelationConverter.toResponse(existing); } @Override @@ -255,32 +258,4 @@ private LogicalTableRelation requireRelation( return relation; } - private LogicalTableRelationResponse mapResponse(LogicalTableRelation relation) { - List sourceColumns = - logicalTableRelationHelper.fromJson( - relation.getSourceColumnNamesJson(), "sourceColumnNames"); - List targetColumns = - logicalTableRelationHelper.fromJson( - relation.getTargetColumnNamesJson(), "targetColumnNames"); - return new LogicalTableRelationResponse( - relation.getId(), - logicalTableRelationHelper.buildRelationKey( - relation.getSourceTableName(), - sourceColumns, - relation.getTargetTableName(), - targetColumns), - relation.getDatasourceId(), - LogicalTableRelationHelper.RELATION_SOURCE_LOGICAL, - relation.getSourceTableName(), - sourceColumns, - relation.getTargetTableName(), - targetColumns, - relation.getRelationType(), - relation.getDescription(), - relation.getIsEnabled(), - relation.getIsEnabled(), - null, - relation.getCreateTime(), - relation.getUpdateTime()); - } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java index 83e8717..ec44199 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java @@ -21,7 +21,6 @@ import io.github.malonetalk.dto.semantic.TableSemanticPageQuery; import io.github.malonetalk.dto.semantic.TableSemanticResponse; import io.github.malonetalk.dto.semantic.TableSemanticUpdateRequest; -import io.github.malonetalk.entity.TableInfo; import java.util.List; public interface TableSemanticService { @@ -30,8 +29,6 @@ public interface TableSemanticService { List listAvailableDomains(Integer datasourceId); - List listTableInfosByDatasourceId(Integer datasourceId); - void updateTableSemantic(TableSemanticUpdateRequest request); void resetTableSemantic(Integer datasourceId, String tableName); diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java index e1223e3..a3dcde1 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java @@ -20,6 +20,7 @@ import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import io.github.malonetalk.common.SemanticConstants; +import io.github.malonetalk.convertor.TableSemanticConverter; import io.github.malonetalk.dto.pagination.PageResponse; import io.github.malonetalk.dto.semantic.TableSemanticPageQuery; import io.github.malonetalk.dto.semantic.TableSemanticResponse; @@ -42,6 +43,7 @@ public class TableSemanticServiceImpl implements TableSemanticService { private final DatasourceService datasourceService; private final TableInfoMapper tableInfoMapper; + private final TableSemanticConverter tableSemanticConverter; @Override public PageResponse getTablePage(TableSemanticPageQuery query) { @@ -65,7 +67,8 @@ public PageResponse getTablePage(TableSemanticPageQuery q SemanticUtils.normalizeBlankToNull(query.keyword()), query.sortOrder()), sortDescending); - List responses = page.stream().map(this::mapResponse).toList(); + List responses = + page.stream().map(tableSemanticConverter::toResponse).toList(); long total = page.getTotal(); return PageResponse.of(responses, total, pageNumber, pageSize); } @@ -85,15 +88,6 @@ public List listAvailableDomains(Integer datasourceId) { .toList(); } - @Override - public List listTableInfosByDatasourceId(Integer datasourceId) { - SemanticUtils.requireDatasourceId(datasourceId); - if (datasourceService.findById(datasourceId) == null) { - return List.of(); - } - return tableInfoMapper.selectByDatasourceId(datasourceId); - } - @Override public void updateTableSemantic(TableSemanticUpdateRequest request) { requireDatasource(request.datasourceId()); @@ -173,20 +167,6 @@ private void requireDatasource(Integer datasourceId) { } } - private TableSemanticResponse mapResponse(TableInfo tableInfo) { - return new TableSemanticResponse( - tableInfo.getId(), - tableInfo.getTableName(), - tableInfo.getDomain(), - null, - tableInfo.getTableDescription(), - tableInfo.getIsVisible(), - true, - Boolean.TRUE.equals(tableInfo.getIsVisible()), - null, - tableInfo.getUpdateTime()); - } - private String normalizeName(String value) { return SemanticUtils.requireName(value, "tableName").toLowerCase(Locale.ROOT); } From dde8a762eba146395552945879fb8ab3f6ff2b98 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Mon, 1 Jun 2026 20:53:45 +0800 Subject: [PATCH 2/9] fix: remove some file --- .../agent/tools/ExecuteSqlTool.java | 8 +- .../agent/tools/GetTableSchemaTool.java | 2 +- .../malonetalk/agent/tools/GetTablesTool.java | 2 +- .../datasource => common}/QueryResult.java | 2 +- .../TableColumnSemanticController.java | 2 +- .../TableRelationSemanticController.java | 2 +- .../controller/TableSemanticController.java | 2 +- .../dto/{pagination => }/PageResponse.java | 2 +- .../semantic/TableSchemaSemanticPrompt.java | 2 +- .../ColumnInfo.java => entity/Column.java} | 4 +- .../TableRelationInfo.java | 2 +- .../datasource => enums}/DataSourceType.java | 2 +- .../DynamicDataSourceManager.java | 3 +- .../SchemaReader.java | 14 +- .../SqlExecutor.java | 6 +- .../mapper/ColumnSemanticInfoMapper.java | 4 + .../malonetalk/mapper/TableInfoMapper.java | 4 + .../semantic/SemanticMergeService.java | 241 +++++++----------- .../column/ColumnSemanticService.java | 2 +- .../column/ColumnSemanticServiceImpl.java | 2 +- .../relation/RelationSemanticService.java | 2 +- .../relation/RelationSemanticServiceImpl.java | 2 +- .../semantic/table/TableSemanticService.java | 2 +- .../table/TableSemanticServiceImpl.java | 2 +- .../mapper/ColumnSemanticInfoMapper.xml | 18 ++ .../main/resources/mapper/TableInfoMapper.xml | 17 ++ 26 files changed, 166 insertions(+), 185 deletions(-) rename data-agent-backend/src/main/java/io/github/malonetalk/{agent/datasource => common}/QueryResult.java (98%) rename data-agent-backend/src/main/java/io/github/malonetalk/dto/{pagination => }/PageResponse.java (98%) rename data-agent-backend/src/main/java/io/github/malonetalk/{agent/datasource/ColumnInfo.java => entity/Column.java} (95%) rename data-agent-backend/src/main/java/io/github/malonetalk/{agent/datasource => entity}/TableRelationInfo.java (95%) rename data-agent-backend/src/main/java/io/github/malonetalk/{agent/datasource => enums}/DataSourceType.java (97%) rename data-agent-backend/src/main/java/io/github/malonetalk/{agent/datasource => infrastructure}/DynamicDataSourceManager.java (97%) rename data-agent-backend/src/main/java/io/github/malonetalk/{agent/datasource => infrastructure}/SchemaReader.java (95%) rename data-agent-backend/src/main/java/io/github/malonetalk/{agent/datasource => infrastructure}/SqlExecutor.java (97%) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java index c7396bf..7cb5765 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java @@ -19,10 +19,10 @@ import io.agentscope.core.tool.Tool; import io.agentscope.core.tool.ToolParam; -import io.github.malonetalk.agent.datasource.QueryResult; -import io.github.malonetalk.agent.datasource.SqlExecutor; -import io.github.malonetalk.agent.datasource.SqlExecutor.SqlExecutionException; -import io.github.malonetalk.agent.datasource.SqlExecutor.SqlSecurityException; +import io.github.malonetalk.common.QueryResult; +import io.github.malonetalk.infrastructure.SqlExecutor; +import io.github.malonetalk.infrastructure.SqlExecutor.SqlExecutionException; +import io.github.malonetalk.infrastructure.SqlExecutor.SqlSecurityException; import io.github.malonetalk.entity.Datasource; import io.github.malonetalk.enums.Status; import io.github.malonetalk.service.DatasourceService; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java index 3928b06..571fdbb 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java @@ -20,7 +20,7 @@ import io.agentscope.core.tool.Tool; import io.agentscope.core.tool.ToolParam; import io.github.malonetalk.common.ToolResult; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.TableSchemaSemanticPrompt; import io.github.malonetalk.service.semantic.SemanticMergeService; import lombok.RequiredArgsConstructor; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java index f19a6a1..8b3aa70 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java @@ -21,7 +21,7 @@ import io.agentscope.core.tool.ToolParam; import io.github.malonetalk.agent.tools.response.TablePromptResponse; import io.github.malonetalk.common.ToolResult; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.service.semantic.SemanticMergeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/QueryResult.java b/data-agent-backend/src/main/java/io/github/malonetalk/common/QueryResult.java similarity index 98% rename from data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/QueryResult.java rename to data-agent-backend/src/main/java/io/github/malonetalk/common/QueryResult.java index e63d13b..520396d 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/QueryResult.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/common/QueryResult.java @@ -15,7 +15,7 @@ * along with this program. If not, see . * limitations under the License. */ -package io.github.malonetalk.agent.datasource; +package io.github.malonetalk.common; import java.util.ArrayList; import java.util.LinkedHashMap; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableColumnSemanticController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableColumnSemanticController.java index d6bb3e3..2e994f8 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableColumnSemanticController.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableColumnSemanticController.java @@ -18,7 +18,7 @@ package io.github.malonetalk.controller; import io.github.malonetalk.common.Result; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.BatchResetColumnSemanticRequest; import io.github.malonetalk.dto.semantic.ColumnSemanticPageQuery; import io.github.malonetalk.dto.semantic.ColumnSemanticResponse; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableRelationSemanticController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableRelationSemanticController.java index bd3ffab..c8379af 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableRelationSemanticController.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableRelationSemanticController.java @@ -18,7 +18,7 @@ package io.github.malonetalk.controller; import io.github.malonetalk.common.Result; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.BatchDeleteLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.BindLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableSemanticController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableSemanticController.java index 14dcb26..a2e1e53 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableSemanticController.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableSemanticController.java @@ -18,7 +18,7 @@ package io.github.malonetalk.controller; import io.github.malonetalk.common.Result; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.BatchResetTableSemanticRequest; import io.github.malonetalk.dto.semantic.TableSemanticPageQuery; import io.github.malonetalk.dto.semantic.TableSemanticResponse; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/pagination/PageResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/PageResponse.java similarity index 98% rename from data-agent-backend/src/main/java/io/github/malonetalk/dto/pagination/PageResponse.java rename to data-agent-backend/src/main/java/io/github/malonetalk/dto/PageResponse.java index e8ff6f8..113304b 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/dto/pagination/PageResponse.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/PageResponse.java @@ -15,7 +15,7 @@ * along with this program. If not, see . * limitations under the License. */ -package io.github.malonetalk.dto.pagination; +package io.github.malonetalk.dto; import java.util.Collections; import java.util.List; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java index 8f04607..cd4ff63 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/TableSchemaSemanticPrompt.java @@ -18,7 +18,7 @@ package io.github.malonetalk.dto.semantic; import io.github.malonetalk.agent.tools.response.ColumnPromptResponse; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; public record TableSchemaSemanticPrompt( String name, diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/ColumnInfo.java b/data-agent-backend/src/main/java/io/github/malonetalk/entity/Column.java similarity index 95% rename from data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/ColumnInfo.java rename to data-agent-backend/src/main/java/io/github/malonetalk/entity/Column.java index 142a8c0..20cd617 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/ColumnInfo.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/entity/Column.java @@ -15,9 +15,9 @@ * along with this program. If not, see . * limitations under the License. */ -package io.github.malonetalk.agent.datasource; +package io.github.malonetalk.entity; -public record ColumnInfo( +public record Column( String columnName, String typeName, int columnSize, diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/TableRelationInfo.java b/data-agent-backend/src/main/java/io/github/malonetalk/entity/TableRelationInfo.java similarity index 95% rename from data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/TableRelationInfo.java rename to data-agent-backend/src/main/java/io/github/malonetalk/entity/TableRelationInfo.java index 1aede31..9e1a90f 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/TableRelationInfo.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/entity/TableRelationInfo.java @@ -15,7 +15,7 @@ * along with this program. If not, see . * limitations under the License. */ -package io.github.malonetalk.agent.datasource; +package io.github.malonetalk.entity; import java.util.List; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/DataSourceType.java b/data-agent-backend/src/main/java/io/github/malonetalk/enums/DataSourceType.java similarity index 97% rename from data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/DataSourceType.java rename to data-agent-backend/src/main/java/io/github/malonetalk/enums/DataSourceType.java index 0e48e00..e599f42 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/DataSourceType.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/enums/DataSourceType.java @@ -15,7 +15,7 @@ * along with this program. If not, see . * limitations under the License. */ -package io.github.malonetalk.agent.datasource; +package io.github.malonetalk.enums; import java.util.Arrays; import java.util.Optional; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/DynamicDataSourceManager.java b/data-agent-backend/src/main/java/io/github/malonetalk/infrastructure/DynamicDataSourceManager.java similarity index 97% rename from data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/DynamicDataSourceManager.java rename to data-agent-backend/src/main/java/io/github/malonetalk/infrastructure/DynamicDataSourceManager.java index 137150c..10579b2 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/DynamicDataSourceManager.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/infrastructure/DynamicDataSourceManager.java @@ -15,11 +15,12 @@ * along with this program. If not, see . * limitations under the License. */ -package io.github.malonetalk.agent.datasource; +package io.github.malonetalk.infrastructure; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import io.github.malonetalk.entity.Datasource; +import io.github.malonetalk.enums.DataSourceType; import jakarta.annotation.PreDestroy; import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/SchemaReader.java b/data-agent-backend/src/main/java/io/github/malonetalk/infrastructure/SchemaReader.java similarity index 95% rename from data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/SchemaReader.java rename to data-agent-backend/src/main/java/io/github/malonetalk/infrastructure/SchemaReader.java index d778fa3..038946f 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/datasource/SchemaReader.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/infrastructure/SchemaReader.java @@ -15,10 +15,8 @@ * along with this program. If not, see . * limitations under the License. */ -package io.github.malonetalk.agent.datasource; +package io.github.malonetalk.infrastructure; -import io.github.malonetalk.entity.Datasource; -import io.github.malonetalk.entity.TableInfo; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -29,6 +27,8 @@ import java.util.List; import java.util.Map; import java.util.Set; + +import io.github.malonetalk.entity.*; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -40,7 +40,7 @@ public class SchemaReader { private final DynamicDataSourceManager dynamicDataSourceManager; - public List getTableSchema(Datasource datasource, String tableName) { + public List getTableSchema(Datasource datasource, String tableName) { javax.sql.DataSource ds = dynamicDataSourceManager.getOrCreateDataSource(datasource); try (Connection conn = ds.getConnection()) { @@ -166,9 +166,9 @@ private Set getPrimaryKeys(Connection conn, String tableName) throws SQL return pkColumns; } - private List getColumns(Connection conn, String tableName, Set primaryKeys) + private List getColumns(Connection conn, String tableName, Set primaryKeys) throws SQLException { - List columns = new ArrayList<>(); + List columns = new ArrayList<>(); DatabaseMetaData metaData = conn.getMetaData(); try (ResultSet rs = @@ -184,7 +184,7 @@ private List getColumns(Connection conn, String tableName, Set. * limitations under the License. */ -package io.github.malonetalk.agent.datasource; +package io.github.malonetalk.infrastructure; -import io.github.malonetalk.entity.Datasource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -27,6 +26,9 @@ import java.util.Map; import java.util.regex.Pattern; import javax.sql.DataSource; + +import io.github.malonetalk.common.QueryResult; +import io.github.malonetalk.entity.Datasource; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/ColumnSemanticInfoMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/ColumnSemanticInfoMapper.java index 470e761..17a950b 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/ColumnSemanticInfoMapper.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/ColumnSemanticInfoMapper.java @@ -35,6 +35,10 @@ List selectPageByDatasourceIdAndTableName( @Param("query") ColumnSemanticPageQuery query, @Param("sortDescending") boolean sortDescending); + List selectVisiblePageByDatasourceIdAndTableName( + @Param("query") ColumnSemanticPageQuery query, + @Param("sortDescending") boolean sortDescending); + ColumnInfo selectByDatasourceIdAndTableNameAndColumnName( @Param("datasourceId") Integer datasourceId, @Param("tableName") String tableName, diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java index 9c41458..a8274f2 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java @@ -39,6 +39,10 @@ List selectPageByDatasourceId( @Param("query") TableSemanticPageQuery query, @Param("sortDescending") boolean sortDescending); + List selectVisiblePageByDatasourceId( + @Param("query") TableSemanticPageQuery query, + @Param("sortDescending") boolean sortDescending); + TableInfo selectByDatasourceIdAndTableName( @Param("datasourceId") Integer datasourceId, @Param("tableName") String tableName); } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java index 4fe18a3..b6eccd0 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java @@ -17,13 +17,18 @@ */ package io.github.malonetalk.service.semantic; -import io.github.malonetalk.agent.datasource.ColumnInfo; -import io.github.malonetalk.agent.datasource.SchemaReader; +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import io.github.malonetalk.entity.Column; +import io.github.malonetalk.infrastructure.SchemaReader; import io.github.malonetalk.agent.tools.response.ColumnPromptResponse; import io.github.malonetalk.agent.tools.response.TablePromptResponse; import io.github.malonetalk.agent.tools.response.TableRelationResponse; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; +import io.github.malonetalk.dto.semantic.ColumnSemanticPageQuery; import io.github.malonetalk.dto.semantic.TableSchemaSemanticPrompt; +import io.github.malonetalk.dto.semantic.TableSemanticPageQuery; +import io.github.malonetalk.entity.ColumnInfo; import io.github.malonetalk.entity.Datasource; import io.github.malonetalk.entity.LogicalTableRelation; import io.github.malonetalk.entity.TableInfo; @@ -36,8 +41,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -57,119 +62,94 @@ public class SemanticMergeService { public PageResponse getVisibleTablePromptPage(int page, int pageSize) { Datasource datasource = datasourceService.requireActiveDatasource(); - List physicalTables = schemaReader.getTables(datasource); - List semanticTables = - tableInfoMapper.selectByDatasourceId(datasource.getId()); - Map semanticByKey = - toTableKeyMap(semanticTables); + PageHelper.startPage(page, pageSize); + Page pageResult = + (Page) + tableInfoMapper.selectVisiblePageByDatasourceId( + new TableSemanticPageQuery( + datasource.getId(), page, pageSize, null, "asc"), + false); - List visibleTables = new ArrayList<>(); - for (TableInfo physicalTable : physicalTables) { - String key = normalizeKey(physicalTable.getTableName()); - TableInfo semanticTable = semanticByKey.get(key); - if (!isTableVisible(semanticTable)) { - continue; - } - String description = - resolveSemanticFirst( - semanticTable != null ? semanticTable.getTableDescription() : null, - physicalTable.getTableDescription()); - String domain = - resolveSemanticFirst( - semanticTable != null ? semanticTable.getDomain() : null, null); - visibleTables.add( - new TablePromptResponse( - physicalTable.getTableName(), - domain, - description, - Collections.emptyList())); - } - - // 为每个可见表附加关系 Map> logicalRelationsBySource = buildLogicalRelationsBySource(datasource.getId()); - for (int i = 0; i < visibleTables.size(); i++) { - TablePromptResponse item = visibleTables.get(i); - List relations = - resolveVisibleRelations( - item.name(), logicalRelationsBySource, datasource.getId()); - visibleTables.set( - i, - new TablePromptResponse( - item.name(), - item.domain(), - item.description(), - relations)); - } - // 内存分页 - return paginateInMemory(visibleTables, page, pageSize); + List items = + pageResult.stream() + .map( + t -> + new TablePromptResponse( + t.getTableName(), + SemanticUtils.normalizeBlankToNull(t.getDomain()), + SemanticUtils.normalizeBlankToNull( + t.getTableDescription()), + resolveVisibleRelations( + t.getTableName(), + logicalRelationsBySource))) + .toList(); + + return PageResponse.of(items, pageResult.getTotal(), page, pageSize); } public TableSchemaSemanticPrompt getTableSchema(String tableName, int page, int pageSize) { Datasource datasource = datasourceService.requireActiveDatasource(); String normalizedTableName = SemanticUtils.requireName(tableName, "tableName"); - // 校验表存在且可见 TableInfo semanticTable = tableInfoMapper.selectByDatasourceIdAndTableName( datasource.getId(), normalizedTableName); - if (!isTableVisible(semanticTable)) { + if (semanticTable == null || !Boolean.TRUE.equals(semanticTable.getIsVisible())) { throw new IllegalArgumentException("表 " + normalizedTableName + " 不存在或不可见。"); } - // 读物理列 - List physicalColumns; + Map physicalByKey = new HashMap<>(); try { - physicalColumns = schemaReader.getTableSchema(datasource, normalizedTableName); + for (Column col : + schemaReader.getTableSchema(datasource, normalizedTableName)) { + physicalByKey.put(col.columnName().toLowerCase(Locale.ROOT), col); + } } catch (SchemaReader.SchemaReadException e) { throw new IllegalArgumentException( "无法读取表 " + normalizedTableName + " 的 Schema: " + e.getMessage(), e); } - - // 读语义列 - if (physicalColumns.isEmpty()) { + if (physicalByKey.isEmpty()) { throw new IllegalArgumentException( "表 " + normalizedTableName + " 不存在或没有可读列。"); } - List semanticColumns = - columnSemanticInfoMapper.selectByDatasourceIdAndTableName( - datasource.getId(), normalizedTableName); - Map semanticByKey = - toColumnKeyMap(semanticColumns); - - // 合并列 - List visibleColumns = new ArrayList<>(); - for (ColumnInfo physicalColumn : physicalColumns) { - String key = normalizeKey(physicalColumn.columnName()); - io.github.malonetalk.entity.ColumnInfo semanticColumn = semanticByKey.get(key); - if (!isColumnVisible(semanticColumn)) { - continue; - } - visibleColumns.add(mapColumnPrompt(physicalColumn, semanticColumn)); - } + PageHelper.startPage(page, pageSize); + Page pageResult = + (Page) + columnSemanticInfoMapper.selectVisiblePageByDatasourceIdAndTableName( + new ColumnSemanticPageQuery( + datasource.getId(), + normalizedTableName, + page, + pageSize, + null, + "asc"), + false); + + List items = + pageResult.stream() + .map(c -> mapColumnPrompt(c, physicalByKey)) + .toList(); String description = - resolveSemanticFirst( - semanticTable != null ? semanticTable.getTableDescription() : null, null); - String domain = - resolveSemanticFirst( - semanticTable != null ? semanticTable.getDomain() : null, null); + SemanticUtils.normalizeBlankToNull(semanticTable.getTableDescription()); + String domain = SemanticUtils.normalizeBlankToNull(semanticTable.getDomain()); PageResponse columnPage = - paginateInMemory(visibleColumns, page, pageSize); + PageResponse.of(items, pageResult.getTotal(), page, pageSize); return new TableSchemaSemanticPrompt( normalizedTableName, domain, description, columnPage); } private List resolveVisibleRelations( String sourceTableName, - Map> logicalRelationsBySource, - Integer datasourceId) { + Map> logicalRelationsBySource) { List logicalRelations = logicalRelationsBySource.getOrDefault( - normalizeKey(sourceTableName), Collections.emptyList()); + sourceTableName.toLowerCase(Locale.ROOT), Collections.emptyList()); if (logicalRelations.isEmpty()) { return Collections.emptyList(); } @@ -188,8 +168,7 @@ private List resolveVisibleRelations( logicalTableRelationHelper.fromJson( relation.getTargetColumnNamesJson(), "targetColumnNames"); } catch (IllegalArgumentException e) { - log.warn( - "跳过无效的逻辑关系 id={}: {}", relation.getId(), e.getMessage()); + log.warn("跳过无效的逻辑关系 id={}: {}", relation.getId(), e.getMessage()); continue; } result.add( @@ -206,91 +185,47 @@ private List resolveVisibleRelations( } private ColumnPromptResponse mapColumnPrompt( - ColumnInfo physicalColumn, - io.github.malonetalk.entity.ColumnInfo semanticColumn) { + ColumnInfo semanticColumn, + Map physicalByKey) { + Column physicalColumn = + physicalByKey.get(semanticColumn.getColumnName().toLowerCase(Locale.ROOT)); String description = - resolveSemanticFirst( - semanticColumn != null ? semanticColumn.getColumnDescription() : null, - physicalColumn.remarks()); - StringBuilder typeText = new StringBuilder(physicalColumn.typeName()); - if (physicalColumn.columnSize() > 0) { - typeText.append("(").append(physicalColumn.columnSize()).append(")"); + SemanticUtils.normalizeBlankToNull(semanticColumn.getColumnDescription()); + if (description == null && physicalColumn != null) { + description = SemanticUtils.normalizeBlankToNull(physicalColumn.remarks()); + } + String typeText = ""; + boolean primaryKey = false; + boolean nullable = true; + String defaultValue = null; + if (physicalColumn != null) { + StringBuilder typeBuilder = new StringBuilder(physicalColumn.typeName()); + if (physicalColumn.columnSize() > 0) { + typeBuilder.append("(").append(physicalColumn.columnSize()).append(")"); + } + typeText = typeBuilder.toString(); + primaryKey = physicalColumn.primaryKey(); + nullable = physicalColumn.nullable(); + defaultValue = SemanticUtils.normalizeBlankToNull(physicalColumn.defaultValue()); } return new ColumnPromptResponse( - physicalColumn.columnName(), - typeText.toString(), - physicalColumn.primaryKey(), - physicalColumn.nullable(), - SemanticUtils.normalizeBlankToNull(physicalColumn.defaultValue()), + semanticColumn.getColumnName(), + typeText, + primaryKey, + nullable, + defaultValue, description); } - private boolean isTableVisible(TableInfo semanticTable) { - if (semanticTable != null) { - return Boolean.TRUE.equals(semanticTable.getIsVisible()); - } - return true; - } - - private boolean isColumnVisible(io.github.malonetalk.entity.ColumnInfo semanticColumn) { - if (semanticColumn != null) { - return Boolean.TRUE.equals(semanticColumn.getIsVisible()); - } - return true; - } - private Map> buildLogicalRelationsBySource( Integer datasourceId) { List allRelations = logicalTableRelationMapper.selectByDatasourceId(datasourceId); Map> result = new HashMap<>(); for (LogicalTableRelation relation : allRelations) { - String key = normalizeKey(relation.getSourceTableName()); + String key = relation.getSourceTableName().toLowerCase(Locale.ROOT); result.computeIfAbsent(key, k -> new ArrayList<>()).add(relation); } return result; } - - private Map toTableKeyMap(List tables) { - Map map = new LinkedHashMap<>(); - for (TableInfo table : tables) { - map.put(normalizeKey(table.getTableName()), table); - } - return map; - } - - private Map toColumnKeyMap( - List columns) { - Map map = new LinkedHashMap<>(); - for (io.github.malonetalk.entity.ColumnInfo column : columns) { - map.put(normalizeKey(column.getColumnName()), column); - } - return map; - } - - private PageResponse paginateInMemory( - List items, int page, int pageSize) { - if (items.isEmpty()) { - return PageResponse.empty(page, pageSize); - } - int start = (page - 1) * pageSize; - if (start >= items.size()) { - return PageResponse.empty(page, pageSize); - } - int end = Math.min(start + pageSize, items.size()); - List pageItems = items.subList(start, end); - return PageResponse.of(pageItems, items.size(), page, pageSize); - } - - private String normalizeKey(String value) { - return logicalTableRelationHelper.normalizeIdentifierKey(value); - } - - private String resolveSemanticFirst(String semanticValue, String physicalValue) { - String resolved = SemanticUtils.normalizeBlankToNull(semanticValue); - if (resolved != null) { - return resolved; - } - return SemanticUtils.normalizeBlankToNull(physicalValue); - } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticService.java index d014abe..cd08abd 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticService.java @@ -17,7 +17,7 @@ */ package io.github.malonetalk.service.semantic.column; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.ColumnSemanticPageQuery; import io.github.malonetalk.dto.semantic.ColumnSemanticResponse; import io.github.malonetalk.dto.semantic.ColumnSemanticUpdateRequest; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java index 478794e..9d1ab4f 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/column/ColumnSemanticServiceImpl.java @@ -21,7 +21,7 @@ import com.github.pagehelper.PageHelper; import io.github.malonetalk.common.SemanticConstants; import io.github.malonetalk.convertor.ColumnSemanticConverter; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.ColumnSemanticPageQuery; import io.github.malonetalk.dto.semantic.ColumnSemanticResponse; import io.github.malonetalk.dto.semantic.ColumnSemanticUpdateRequest; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java index bafecff..76ebfe8 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java @@ -17,7 +17,7 @@ */ package io.github.malonetalk.service.semantic.relation; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.BindLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; import io.github.malonetalk.dto.semantic.RelationSemanticPageQuery; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java index 07ff629..7ee9c9d 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java @@ -21,7 +21,7 @@ import com.github.pagehelper.PageHelper; import io.github.malonetalk.common.SemanticConstants; import io.github.malonetalk.convertor.LogicalTableRelationConverter; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.BindLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; import io.github.malonetalk.dto.semantic.RelationSemanticPageQuery; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java index ec44199..997d7d6 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java @@ -17,7 +17,7 @@ */ package io.github.malonetalk.service.semantic.table; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.TableSemanticPageQuery; import io.github.malonetalk.dto.semantic.TableSemanticResponse; import io.github.malonetalk.dto.semantic.TableSemanticUpdateRequest; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java index a3dcde1..f589554 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java @@ -21,7 +21,7 @@ import com.github.pagehelper.PageHelper; import io.github.malonetalk.common.SemanticConstants; import io.github.malonetalk.convertor.TableSemanticConverter; -import io.github.malonetalk.dto.pagination.PageResponse; +import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.TableSemanticPageQuery; import io.github.malonetalk.dto.semantic.TableSemanticResponse; import io.github.malonetalk.dto.semantic.TableSemanticUpdateRequest; diff --git a/data-agent-backend/src/main/resources/mapper/ColumnSemanticInfoMapper.xml b/data-agent-backend/src/main/resources/mapper/ColumnSemanticInfoMapper.xml index eb3cf09..359991d 100644 --- a/data-agent-backend/src/main/resources/mapper/ColumnSemanticInfoMapper.xml +++ b/data-agent-backend/src/main/resources/mapper/ColumnSemanticInfoMapper.xml @@ -43,6 +43,24 @@ + + + + - + INSERT INTO column_info ( datasource_id, table_name, column_name, column_description, is_visible, create_time, update_time @@ -80,7 +80,7 @@ ) - + UPDATE column_info table_name = #{tableName}, diff --git a/data-agent-backend/src/main/resources/mapper/TableInfoMapper.xml b/data-agent-backend/src/main/resources/mapper/TableSemanticMapper.xml similarity index 95% rename from data-agent-backend/src/main/resources/mapper/TableInfoMapper.xml rename to data-agent-backend/src/main/resources/mapper/TableSemanticMapper.xml index c205d74..cb376ef 100644 --- a/data-agent-backend/src/main/resources/mapper/TableInfoMapper.xml +++ b/data-agent-backend/src/main/resources/mapper/TableSemanticMapper.xml @@ -1,8 +1,8 @@ - + - + @@ -60,7 +60,7 @@ LIMIT 1 - + INSERT INTO table_info ( table_name, table_description, domain, datasource_id, is_visible, create_time, update_time @@ -70,7 +70,7 @@ ) - + UPDATE table_info table_name = #{tableName}, From e3e6f15cad6114685a500e961371198bfce2055b Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Mon, 1 Jun 2026 21:42:30 +0800 Subject: [PATCH 4/9] fix: rename --- ...umnSemanticController.java => ColumnSemanticController.java} | 2 +- ...nSemanticController.java => RelationSemanticController.java} | 2 +- .../malonetalk/convertor/LogicalTableRelationConverter.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename data-agent-backend/src/main/java/io/github/malonetalk/controller/{TableColumnSemanticController.java => ColumnSemanticController.java} (98%) rename data-agent-backend/src/main/java/io/github/malonetalk/controller/{TableRelationSemanticController.java => RelationSemanticController.java} (99%) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableColumnSemanticController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/ColumnSemanticController.java similarity index 98% rename from data-agent-backend/src/main/java/io/github/malonetalk/controller/TableColumnSemanticController.java rename to data-agent-backend/src/main/java/io/github/malonetalk/controller/ColumnSemanticController.java index 2e994f8..4b8e1db 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableColumnSemanticController.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/ColumnSemanticController.java @@ -41,7 +41,7 @@ @RestController @RequestMapping("/api/semantic/tables/columns/{tableName}") @RequiredArgsConstructor -public class TableColumnSemanticController { +public class ColumnSemanticController { private final ColumnSemanticService columnSemanticService; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableRelationSemanticController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/RelationSemanticController.java similarity index 99% rename from data-agent-backend/src/main/java/io/github/malonetalk/controller/TableRelationSemanticController.java rename to data-agent-backend/src/main/java/io/github/malonetalk/controller/RelationSemanticController.java index c8379af..f436660 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/TableRelationSemanticController.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/RelationSemanticController.java @@ -44,7 +44,7 @@ @RestController @RequestMapping("/api/semantic/tables/relations/{tableName}") @RequiredArgsConstructor -public class TableRelationSemanticController { +public class RelationSemanticController { private final RelationSemanticService relationSemanticService; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java index e41e79d..c690c8d 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/convertor/LogicalTableRelationConverter.java @@ -53,7 +53,7 @@ public LogicalTableRelationResponse toResponse(LogicalTableRelation relation) { relation.getRelationType(), relation.getDescription(), relation.getIsEnabled(), - relation.getIsEnabled(), + Boolean.TRUE.equals(relation.getIsEnabled()), null, relation.getCreateTime(), relation.getUpdateTime()); From 98a4cc3928741d7d1b8bb184d5408c30de019296 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 2 Jun 2026 14:10:53 +0800 Subject: [PATCH 5/9] feat: enhance semantic in expection --- .../RelationSemanticController.java | 37 ++- .../RelationCandidateColumnResponse.java | 21 ++ .../RelationCandidateTableResponse.java | 20 ++ .../exception/GlobalExceptionHandler.java | 7 + .../exception/SemanticSchemaException.java | 29 +++ .../semantic/SemanticMergeService.java | 110 +++++++- .../relation/RelationSemanticService.java | 8 + .../relation/RelationSemanticServiceImpl.java | 242 ++++++++++++++++++ 8 files changed, 459 insertions(+), 15 deletions(-) create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateColumnResponse.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateTableResponse.java create mode 100644 data-agent-backend/src/main/java/io/github/malonetalk/exception/SemanticSchemaException.java diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/RelationSemanticController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/RelationSemanticController.java index f436660..2273ed4 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/controller/RelationSemanticController.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/RelationSemanticController.java @@ -22,6 +22,8 @@ import io.github.malonetalk.dto.semantic.BatchDeleteLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.BindLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; +import io.github.malonetalk.dto.semantic.RelationCandidateColumnResponse; +import io.github.malonetalk.dto.semantic.RelationCandidateTableResponse; import io.github.malonetalk.dto.semantic.RelationSemanticPageQuery; import io.github.malonetalk.dto.semantic.UpdateLogicalTableRelationEnabledRequest; import io.github.malonetalk.dto.semantic.UpdateLogicalTableRelationRequest; @@ -42,13 +44,13 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/api/semantic/tables/relations/{tableName}") +@RequestMapping("/api/semantic/tables") @RequiredArgsConstructor public class RelationSemanticController { private final RelationSemanticService relationSemanticService; - @GetMapping + @GetMapping("/{tableName}/relations") public Result> listByTable( @PathVariable @NotBlank String tableName, @Valid RelationSemanticPageQuery query) { return Result.success( @@ -63,21 +65,21 @@ public Result> listByTable( query.sortOrder()))); } - @PostMapping + @PostMapping("/{tableName}/relations") public Result create( @PathVariable @NotBlank String tableName, @Valid @RequestBody BindLogicalTableRelationRequest request) { return Result.success(relationSemanticService.createRelationSemantic(tableName, request)); } - @PutMapping + @PutMapping("/{tableName}/relations") public Result update( @PathVariable @NotBlank String tableName, @Valid @RequestBody UpdateLogicalTableRelationRequest request) { return Result.success(relationSemanticService.updateRelationSemantic(tableName, request)); } - @PutMapping("/enabled") + @PutMapping("/{tableName}/relations/enabled") public Result updateEnabled( @PathVariable @NotBlank String tableName, @Valid @RequestBody UpdateLogicalTableRelationEnabledRequest request) { @@ -85,7 +87,7 @@ public Result updateEnabled( relationSemanticService.updateRelationSemanticEnabled(tableName, request)); } - @DeleteMapping("/{relationId}") + @DeleteMapping("/{tableName}/relations/{relationId}") public Result delete( @PathVariable @NotBlank String tableName, @PathVariable @NotNull @Min(1) Integer relationId, @@ -95,7 +97,7 @@ public Result delete( datasourceId, tableName, relationId)); } - @DeleteMapping("/batch") + @DeleteMapping("/{tableName}/relations/batch") public Result deleteBatch( @PathVariable @NotBlank String tableName, @Valid @RequestBody BatchDeleteLogicalTableRelationRequest request) { @@ -103,4 +105,25 @@ public Result deleteBatch( relationSemanticService.deleteRelationSemantics( request.datasourceId(), tableName, request.relationIds())); } + + @GetMapping("/relations/candidate/tables") + public Result> candidateTables( + @Valid RelationSemanticPageQuery query) { + return Result.success(relationSemanticService.getCandidateTablePage(query)); + } + + @GetMapping("/{tableName}/relations/candidate/columns") + public Result> candidateColumns( + @PathVariable @NotBlank String tableName, @Valid RelationSemanticPageQuery query) { + return Result.success( + relationSemanticService.getCandidateColumnPage( + new RelationSemanticPageQuery( + query.datasourceId(), + tableName, + query.page(), + query.pageSize(), + query.keyword(), + query.enabled(), + query.sortOrder()))); + } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateColumnResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateColumnResponse.java new file mode 100644 index 0000000..a201b1e --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateColumnResponse.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.dto.semantic; + +public record RelationCandidateColumnResponse( + String columnName, String description, String typeName, Boolean primaryKey) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateTableResponse.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateTableResponse.java new file mode 100644 index 0000000..56f355f --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/semantic/RelationCandidateTableResponse.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.dto.semantic; + +public record RelationCandidateTableResponse(String tableName, String domain, String description) {} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java b/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java index df0c536..e0cc323 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java @@ -39,4 +39,11 @@ public Result handleBadRequest(Exception exception) { String message = exception.getMessage() == null ? "Bad request" : exception.getMessage(); return Result.error(400, message); } + + @ExceptionHandler(SemanticSchemaException.class) + public Result handleSemanticSchema(SemanticSchemaException exception) { + String message = + exception.getMessage() == null ? "Semantic schema error" : exception.getMessage(); + return Result.error(400, message); + } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/exception/SemanticSchemaException.java b/data-agent-backend/src/main/java/io/github/malonetalk/exception/SemanticSchemaException.java new file mode 100644 index 0000000..67c6cad --- /dev/null +++ b/data-agent-backend/src/main/java/io/github/malonetalk/exception/SemanticSchemaException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2026 github.com/MaloneTalk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * limitations under the License. + */ +package io.github.malonetalk.exception; + +public class SemanticSchemaException extends RuntimeException { + + public SemanticSchemaException(String message) { + super(message); + } + + public SemanticSchemaException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java index 69bc25d..90080e0 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/SemanticMergeService.java @@ -30,6 +30,7 @@ import io.github.malonetalk.entity.ColumnSemantic; import io.github.malonetalk.entity.Datasource; import io.github.malonetalk.entity.LogicalTableRelation; +import io.github.malonetalk.entity.TableRelationInfo; import io.github.malonetalk.entity.TableSemantic; import io.github.malonetalk.infrastructure.SchemaReader; import io.github.malonetalk.mapper.ColumnSemanticMapper; @@ -41,6 +42,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -84,7 +86,8 @@ public PageResponse getVisibleTablePromptPage(int page, int t.getTableDescription()), resolveVisibleRelations( t.getTableName(), - logicalRelationsBySource))) + logicalRelationsBySource, + datasource))) .toList(); return PageResponse.of(items, pageResult.getTotal(), page, pageSize); @@ -141,14 +144,39 @@ public TableSchemaSemanticPrompt getTableSchema(String tableName, int page, int private List resolveVisibleRelations( String sourceTableName, - Map> logicalRelationsBySource) { + Map> logicalRelationsBySource, + Datasource datasource) { + LinkedHashMap merged = new LinkedHashMap<>(); + + // 先添加物理 FK 关系 + List physicalRelations = + schemaReader.getImportedRelations(datasource, sourceTableName); + for (TableRelationInfo phys : physicalRelations) { + if (!areEndpointsVisible(datasource.getId(), phys)) { + continue; + } + String key = + buildRelationMergeKey( + phys.sourceTableName(), + phys.sourceColumnNames(), + phys.targetTableName(), + phys.targetColumnNames()); + merged.put( + key, + new TableRelationResponse( + phys.relationType(), + "physical", + phys.sourceTableName(), + phys.sourceColumnNames(), + phys.targetTableName(), + phys.targetColumnNames(), + phys.description())); + } + + // 逻辑关系覆盖物理关系 List logicalRelations = logicalRelationsBySource.getOrDefault( sourceTableName.toLowerCase(Locale.ROOT), Collections.emptyList()); - if (logicalRelations.isEmpty()) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); for (LogicalTableRelation relation : logicalRelations) { if (!Boolean.TRUE.equals(relation.getIsEnabled())) { continue; @@ -166,7 +194,14 @@ private List resolveVisibleRelations( log.warn("跳过无效的逻辑关系 id={}: {}", relation.getId(), e.getMessage()); continue; } - result.add( + String key = + buildRelationMergeKey( + relation.getSourceTableName(), + sourceColumns, + relation.getTargetTableName(), + targetColumns); + merged.put( + key, new TableRelationResponse( relation.getRelationType(), LogicalTableRelationHelper.RELATION_SOURCE_LOGICAL, @@ -176,7 +211,66 @@ private List resolveVisibleRelations( targetColumns, relation.getDescription())); } - return result; + + return List.copyOf(merged.values()); + } + + private boolean areEndpointsVisible(Integer datasourceId, TableRelationInfo relation) { + TableSemantic sourceTable = + tableSemanticMapper.selectByDatasourceIdAndTableName( + datasourceId, relation.sourceTableName()); + if (sourceTable != null && !Boolean.TRUE.equals(sourceTable.getIsVisible())) { + return false; + } + TableSemantic targetTable = + tableSemanticMapper.selectByDatasourceIdAndTableName( + datasourceId, relation.targetTableName()); + if (targetTable != null && !Boolean.TRUE.equals(targetTable.getIsVisible())) { + return false; + } + for (String colName : relation.sourceColumnNames()) { + ColumnSemantic col = + columnSemanticMapper.selectByDatasourceIdAndTableNameAndColumnName( + datasourceId, relation.sourceTableName(), colName); + if (col != null && !Boolean.TRUE.equals(col.getIsVisible())) { + return false; + } + } + for (String colName : relation.targetColumnNames()) { + ColumnSemantic col = + columnSemanticMapper.selectByDatasourceIdAndTableNameAndColumnName( + datasourceId, relation.targetTableName(), colName); + if (col != null && !Boolean.TRUE.equals(col.getIsVisible())) { + return false; + } + } + return true; + } + + private String buildRelationMergeKey( + String sourceTable, + List sourceColumns, + String targetTable, + List targetColumns) { + String sourceColSig = + sourceColumns.stream() + .map(c -> c.toLowerCase(Locale.ROOT)) + .sorted() + .reduce((a, b) -> a + "|" + b) + .orElse(""); + String targetColSig = + targetColumns.stream() + .map(c -> c.toLowerCase(Locale.ROOT)) + .sorted() + .reduce((a, b) -> a + "|" + b) + .orElse(""); + return sourceTable.toLowerCase(Locale.ROOT) + + "|" + + sourceColSig + + "|" + + targetTable.toLowerCase(Locale.ROOT) + + "|" + + targetColSig; } private ColumnPromptResponse mapColumnPrompt( diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java index 76ebfe8..96c3916 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticService.java @@ -20,6 +20,8 @@ import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.BindLogicalTableRelationRequest; import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; +import io.github.malonetalk.dto.semantic.RelationCandidateColumnResponse; +import io.github.malonetalk.dto.semantic.RelationCandidateTableResponse; import io.github.malonetalk.dto.semantic.RelationSemanticPageQuery; import io.github.malonetalk.dto.semantic.UpdateLogicalTableRelationEnabledRequest; import io.github.malonetalk.dto.semantic.UpdateLogicalTableRelationRequest; @@ -41,4 +43,10 @@ boolean updateRelationSemanticEnabled( boolean deleteRelationSemantic(Integer datasourceId, String tableName, Integer relationId); int deleteRelationSemantics(Integer datasourceId, String tableName, List relationIds); + + PageResponse getCandidateTablePage( + RelationSemanticPageQuery query); + + PageResponse getCandidateColumnPage( + RelationSemanticPageQuery query); } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java index c230e59..28f7273 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/relation/RelationSemanticServiceImpl.java @@ -23,20 +23,37 @@ import io.github.malonetalk.convertor.LogicalTableRelationConverter; import io.github.malonetalk.dto.PageResponse; import io.github.malonetalk.dto.semantic.BindLogicalTableRelationRequest; +import io.github.malonetalk.dto.semantic.ColumnSemanticPageQuery; import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse; +import io.github.malonetalk.dto.semantic.RelationCandidateColumnResponse; +import io.github.malonetalk.dto.semantic.RelationCandidateTableResponse; import io.github.malonetalk.dto.semantic.RelationSemanticPageQuery; +import io.github.malonetalk.dto.semantic.TableSemanticPageQuery; import io.github.malonetalk.dto.semantic.UpdateLogicalTableRelationEnabledRequest; import io.github.malonetalk.dto.semantic.UpdateLogicalTableRelationRequest; +import io.github.malonetalk.entity.Column; +import io.github.malonetalk.entity.ColumnSemantic; +import io.github.malonetalk.entity.Datasource; import io.github.malonetalk.entity.LogicalTableRelation; +import io.github.malonetalk.entity.TableSemantic; +import io.github.malonetalk.exception.SemanticSchemaException; +import io.github.malonetalk.infrastructure.SchemaReader; +import io.github.malonetalk.mapper.ColumnSemanticMapper; import io.github.malonetalk.mapper.LogicalTableRelationMapper; +import io.github.malonetalk.mapper.TableSemanticMapper; import io.github.malonetalk.service.DatasourceService; import io.github.malonetalk.utils.SemanticUtils; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service @RequiredArgsConstructor public class RelationSemanticServiceImpl implements RelationSemanticService { @@ -45,6 +62,9 @@ public class RelationSemanticServiceImpl implements RelationSemanticService { private final LogicalTableRelationMapper logicalTableRelationMapper; private final LogicalTableRelationHelper logicalTableRelationHelper; private final LogicalTableRelationConverter logicalTableRelationConverter; + private final TableSemanticMapper tableSemanticMapper; + private final ColumnSemanticMapper columnSemanticMapper; + private final SchemaReader schemaReader; @Override public PageResponse getRelationPage( @@ -83,6 +103,12 @@ public PageResponse getRelationPage( public LogicalTableRelationResponse createRelationSemantic( String tableName, BindLogicalTableRelationRequest request) { requireDatasource(request.datasourceId()); + validateEndpoints( + request.datasourceId(), + tableName, + request.sourceColumnNames(), + request.targetTableName(), + request.targetColumnNames()); LogicalTableRelation relation = buildRelation(request.datasourceId(), tableName, request); ensureUniqueSourceKey( request.datasourceId(), @@ -98,6 +124,12 @@ public LogicalTableRelationResponse createRelationSemantic( public LogicalTableRelationResponse updateRelationSemantic( String tableName, UpdateLogicalTableRelationRequest request) { requireDatasource(request.datasourceId()); + validateEndpoints( + request.datasourceId(), + tableName, + request.sourceColumnNames(), + request.targetTableName(), + request.targetColumnNames()); LogicalTableRelation existing = requireRelation(request.datasourceId(), tableName, request.relationId()); applyRelationUpdate(existing, tableName, request); @@ -169,6 +201,108 @@ public int deleteRelationSemantics( datasourceId, normalizedTableName, relationIds); } + @Override + public PageResponse getCandidateTablePage( + RelationSemanticPageQuery query) { + requireDatasource(query.datasourceId()); + int pageNumber = PageResponse.resolvePage(query.page()); + int pageSize = PageResponse.resolvePageSize(query.pageSize()); + SemanticUtils.validateSortOrder(query.sortOrder()); + boolean sortDescending = + SemanticConstants.SORT_ORDER_DESC.equalsIgnoreCase(query.sortOrder()); + PageHelper.startPage(pageNumber, pageSize); + Page page = + (Page) + tableSemanticMapper.selectVisiblePageByDatasourceId( + new TableSemanticPageQuery( + query.datasourceId(), + pageNumber, + pageSize, + SemanticUtils.normalizeBlankToNull(query.keyword()), + query.sortOrder()), + sortDescending); + List items = + page.stream() + .map( + t -> + new RelationCandidateTableResponse( + t.getTableName(), + SemanticUtils.normalizeBlankToNull(t.getDomain()), + SemanticUtils.normalizeBlankToNull( + t.getTableDescription()))) + .toList(); + return PageResponse.of(items, page.getTotal(), pageNumber, pageSize); + } + + @Override + public PageResponse getCandidateColumnPage( + RelationSemanticPageQuery query) { + requireDatasource(query.datasourceId()); + String normalizedTableName = + logicalTableRelationHelper.normalizeTableName(query.tableName(), "tableName"); + TableSemantic table = + tableSemanticMapper.selectByDatasourceIdAndTableName( + query.datasourceId(), normalizedTableName); + if (table == null || !Boolean.TRUE.equals(table.getIsVisible())) { + throw new IllegalArgumentException("表 " + normalizedTableName + " 不存在或不可见。"); + } + int pageNumber = PageResponse.resolvePage(query.page()); + int pageSize = PageResponse.resolvePageSize(query.pageSize()); + SemanticUtils.validateSortOrder(query.sortOrder()); + boolean sortDescending = + SemanticConstants.SORT_ORDER_DESC.equalsIgnoreCase(query.sortOrder()); + PageHelper.startPage(pageNumber, pageSize); + Page page = + (Page) + columnSemanticMapper.selectVisiblePageByDatasourceIdAndTableName( + new ColumnSemanticPageQuery( + query.datasourceId(), + normalizedTableName, + pageNumber, + pageSize, + SemanticUtils.normalizeBlankToNull(query.keyword()), + query.sortOrder()), + sortDescending); + + // 物理列信息补充 + Map physicalByKey = new HashMap<>(); + try { + for (Column col : + schemaReader.getTableSchema( + datasourceService.findById(query.datasourceId()), + normalizedTableName)) { + physicalByKey.put(col.columnName().toLowerCase(Locale.ROOT), col); + } + } catch (SchemaReader.SchemaReadException e) { + log.warn("无法读取表 {} 的物理列信息: {}", normalizedTableName, e.getMessage()); + } + + List items = + page.stream() + .map( + c -> { + Column phys = + physicalByKey.get( + c.getColumnName().toLowerCase(Locale.ROOT)); + return new RelationCandidateColumnResponse( + c.getColumnName(), + SemanticUtils.normalizeBlankToNull( + c.getColumnDescription()), + phys != null ? buildTypeText(phys) : null, + phys != null ? phys.primaryKey() : null); + }) + .toList(); + return PageResponse.of(items, page.getTotal(), pageNumber, pageSize); + } + + private static String buildTypeText(Column physicalColumn) { + StringBuilder sb = new StringBuilder(physicalColumn.typeName()); + if (physicalColumn.columnSize() > 0) { + sb.append("(").append(physicalColumn.columnSize()).append(")"); + } + return sb.toString(); + } + private void requireDatasource(Integer datasourceId) { SemanticUtils.requireDatasourceId(datasourceId); if (datasourceService.findById(datasourceId) == null) { @@ -176,6 +310,114 @@ private void requireDatasource(Integer datasourceId) { } } + private void validateEndpoints( + Integer datasourceId, + String sourceTableName, + List sourceColumnNames, + String targetTableName, + List targetColumnNames) { + Datasource datasource = datasourceService.findById(datasourceId); + if (datasource == null) { + throw new IllegalArgumentException("Datasource does not exist: " + datasourceId); + } + String normalizedSourceTableName = + logicalTableRelationHelper.normalizeTableName(sourceTableName, "sourceTableName"); + String normalizedTargetTableName = + logicalTableRelationHelper.normalizeTableName(targetTableName, "targetTableName"); + List normalizedSourceColumnNames = + logicalTableRelationHelper.normalizeColumnNames( + sourceColumnNames, "sourceColumnNames"); + List normalizedTargetColumnNames = + logicalTableRelationHelper.normalizeColumnNames( + targetColumnNames, "targetColumnNames"); + TableSemantic sourceTable = + tableSemanticMapper.selectByDatasourceIdAndTableName( + datasourceId, normalizedSourceTableName); + if (sourceTable == null || !Boolean.TRUE.equals(sourceTable.getIsVisible())) { + throw new IllegalArgumentException("源表 " + normalizedSourceTableName + " 不存在或不可见。"); + } + TableSemantic targetTable = + tableSemanticMapper.selectByDatasourceIdAndTableName( + datasourceId, normalizedTargetTableName); + if (targetTable == null || !Boolean.TRUE.equals(targetTable.getIsVisible())) { + throw new IllegalArgumentException("目标表 " + normalizedTargetTableName + " 不存在或不可见。"); + } + for (String columnName : normalizedSourceColumnNames) { + ColumnSemantic col = + columnSemanticMapper.selectByDatasourceIdAndTableNameAndColumnName( + datasourceId, normalizedSourceTableName, columnName); + if (col == null || !Boolean.TRUE.equals(col.getIsVisible())) { + throw new IllegalArgumentException( + "源列 " + normalizedSourceTableName + "." + columnName + " 不存在或不可见。"); + } + } + for (String columnName : normalizedTargetColumnNames) { + ColumnSemantic col = + columnSemanticMapper.selectByDatasourceIdAndTableNameAndColumnName( + datasourceId, normalizedTargetTableName, columnName); + if (col == null || !Boolean.TRUE.equals(col.getIsVisible())) { + throw new IllegalArgumentException( + "目标列 " + normalizedTargetTableName + "." + columnName + " 不存在或不可见。"); + } + } + + validatePhysicalSchema( + datasource, + normalizedSourceTableName, + normalizedSourceColumnNames, + normalizedTargetTableName, + normalizedTargetColumnNames); + } + + private void validatePhysicalSchema( + Datasource datasource, + String sourceTableName, + List sourceColumnNames, + String targetTableName, + List targetColumnNames) { + Map sourceSchema = loadSchema(datasource, sourceTableName); + Map targetSchema = loadSchema(datasource, targetTableName); + if (sourceSchema.isEmpty()) { + throw new SemanticSchemaException( + "Physical source table does not exist: " + sourceTableName); + } + if (targetSchema.isEmpty()) { + throw new SemanticSchemaException( + "Physical target table does not exist: " + targetTableName); + } + for (String columnName : sourceColumnNames) { + if (!sourceSchema.containsKey(columnName.toLowerCase(Locale.ROOT))) { + throw new IllegalArgumentException( + "Physical source column does not exist: " + + sourceTableName + + "." + + columnName); + } + } + for (String columnName : targetColumnNames) { + if (!targetSchema.containsKey(columnName.toLowerCase(Locale.ROOT))) { + throw new IllegalArgumentException( + "Physical target column does not exist: " + + targetTableName + + "." + + columnName); + } + } + } + + private Map loadSchema(Datasource datasource, String tableName) { + Map physicalByKey = new HashMap<>(); + try { + for (Column column : schemaReader.getTableSchema(datasource, tableName)) { + physicalByKey.put(column.columnName().toLowerCase(Locale.ROOT), column); + } + } catch (SchemaReader.SchemaReadException e) { + throw new SemanticSchemaException( + "Failed to read physical schema for table " + tableName, e); + } + return physicalByKey; + } + private LogicalTableRelation buildRelation( Integer datasourceId, String tableName, BindLogicalTableRelationRequest request) { LogicalTableRelation relation = new LogicalTableRelation(); From 900e1ddf81f2a287e6e25b1e0b59926b6973b406 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Tue, 2 Jun 2026 14:15:00 +0800 Subject: [PATCH 6/9] fix: use chinese in sqltool --- .../agent/tools/ExecuteSqlTool.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java index fcbabb0..2fb2a24 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java @@ -42,17 +42,15 @@ public class ExecuteSqlTool implements MarkAgentTool { @Tool( name = "execute_sql", description = - "Execute SELECT SQL query on the target datasource and return the query result." - + " Only supports SELECT queries, does not support INSERT/UPDATE/DELETE or" - + " other modification operations.") + "在当前活跃数据源上执行 SELECT SQL 查询,并返回查询结果。" + + "仅支持 SELECT 查询,不支持 INSERT、UPDATE、DELETE 或其他修改数据的操作。") public String executeSql( - @ToolParam(name = "sql", description = "The SELECT SQL query statement to execute") - String sql) { + @ToolParam(name = "sql", description = "要执行的 SELECT SQL 查询语句") String sql) { List activeDataSources = dataSourceService.findByStatus(Status.ACTIVE.getCode()); if (activeDataSources.isEmpty()) { - return "No active datasource available, cannot execute SQL."; + return "没有可用的活跃数据源,无法执行 SQL。"; } if (activeDataSources.size() > 1) { @@ -66,28 +64,28 @@ public String executeSql( QueryResult result = sqlExecutor.execute(datasource, sql); return formatResult(result); } catch (SqlSecurityException e) { - return "SQL execution denied: " + e.getMessage(); + return "SQL 执行被拒绝:" + e.getMessage(); } catch (SqlExecutionException e) { - return "SQL execution failed: " + e.getMessage(); + return "SQL 执行失败:" + e.getMessage(); } } private String formatResult(QueryResult result) { if (result.rows().isEmpty()) { - return "Query result is empty."; + return "查询结果为空。"; } StringBuilder sb = new StringBuilder(); - sb.append("Query result (total ").append(result.totalRows()).append(" rows)"); + sb.append("查询结果(共 ").append(result.totalRows()).append(" 行)"); if (result.truncated()) { - sb.append(", truncated to show first ").append(result.rows().size()).append(" rows"); + sb.append(",结果已截断,仅展示前 ").append(result.rows().size()).append(" 行"); } sb.append(":\n"); - sb.append("Columns: ").append(result.columns()).append("\n"); + sb.append("列:").append(result.columns()).append("\n"); for (int i = 0; i < result.rows().size(); i++) { - sb.append("Row ").append(i + 1).append(": ").append(result.rows().get(i)).append("\n"); + sb.append("第 ").append(i + 1).append(" 行:").append(result.rows().get(i)).append("\n"); } return sb.toString(); From 1bf4a7b63986edfde96e2f600207a509ad447e93 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 3 Jun 2026 13:48:45 +0800 Subject: [PATCH 7/9] Revert "fix: use chinese in sqltool" This reverts commit 900e1ddf81f2a287e6e25b1e0b59926b6973b406. --- .../agent/tools/ExecuteSqlTool.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java index 2fb2a24..fcbabb0 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java @@ -42,15 +42,17 @@ public class ExecuteSqlTool implements MarkAgentTool { @Tool( name = "execute_sql", description = - "在当前活跃数据源上执行 SELECT SQL 查询,并返回查询结果。" - + "仅支持 SELECT 查询,不支持 INSERT、UPDATE、DELETE 或其他修改数据的操作。") + "Execute SELECT SQL query on the target datasource and return the query result." + + " Only supports SELECT queries, does not support INSERT/UPDATE/DELETE or" + + " other modification operations.") public String executeSql( - @ToolParam(name = "sql", description = "要执行的 SELECT SQL 查询语句") String sql) { + @ToolParam(name = "sql", description = "The SELECT SQL query statement to execute") + String sql) { List activeDataSources = dataSourceService.findByStatus(Status.ACTIVE.getCode()); if (activeDataSources.isEmpty()) { - return "没有可用的活跃数据源,无法执行 SQL。"; + return "No active datasource available, cannot execute SQL."; } if (activeDataSources.size() > 1) { @@ -64,28 +66,28 @@ public String executeSql( QueryResult result = sqlExecutor.execute(datasource, sql); return formatResult(result); } catch (SqlSecurityException e) { - return "SQL 执行被拒绝:" + e.getMessage(); + return "SQL execution denied: " + e.getMessage(); } catch (SqlExecutionException e) { - return "SQL 执行失败:" + e.getMessage(); + return "SQL execution failed: " + e.getMessage(); } } private String formatResult(QueryResult result) { if (result.rows().isEmpty()) { - return "查询结果为空。"; + return "Query result is empty."; } StringBuilder sb = new StringBuilder(); - sb.append("查询结果(共 ").append(result.totalRows()).append(" 行)"); + sb.append("Query result (total ").append(result.totalRows()).append(" rows)"); if (result.truncated()) { - sb.append(",结果已截断,仅展示前 ").append(result.rows().size()).append(" 行"); + sb.append(", truncated to show first ").append(result.rows().size()).append(" rows"); } sb.append(":\n"); - sb.append("列:").append(result.columns()).append("\n"); + sb.append("Columns: ").append(result.columns()).append("\n"); for (int i = 0; i < result.rows().size(); i++) { - sb.append("第 ").append(i + 1).append(" 行:").append(result.rows().get(i)).append("\n"); + sb.append("Row ").append(i + 1).append(": ").append(result.rows().get(i)).append("\n"); } return sb.toString(); From 475a0487a34139fc2aa759ae58087ebbeaed02f2 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 3 Jun 2026 14:10:45 +0800 Subject: [PATCH 8/9] feat: remove chinese --- .../agent/tools/GetTableSchemaTool.java | 23 ++++++++++--------- .../malonetalk/agent/tools/GetTablesTool.java | 18 ++++++--------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java index bb4d187..5578fad 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java @@ -37,14 +37,15 @@ public class GetTableSchemaTool implements MarkAgentTool { @Tool( name = "get_table_schema", description = - "获取指定表的语义 Schema 信息,包含表元数据和分页可见列信息。" - + "语义描述优先于物理元数据作为兜底。" - + "表关系通过 get_tables 工具获取,不通过本工具返回。" - + "在生成 SQL 之前应调用此工具了解表结构。") + "Get the schema information of the specified table, including column name, data" + + " type, whether it is primary key, whether it allows null, default value" + + " and column comments. This tool should be called to understand the table" + + " structure before generating SQL.") public ToolResult getTableSchema( - @ToolParam(name = "table_name", description = "要查询 Schema 的表名") String tableName, - @ToolParam(name = "column_page", description = "可选列页码,默认为1") Integer columnPage, - @ToolParam(name = "column_page_size", description = "可选列每页大小,默认20,最大100") + @ToolParam(name = "table_name", description = "The table name to query schema for") + String tableName, + @ToolParam(name = "column_page", description = "Optional column page number, default is 1") Integer columnPage, + @ToolParam(name = "column_page_size", description = "Optional column page size, default is 20, maximum is 100") Integer columnPageSize) { try { int resolvedPage = PageResponse.resolvePage(columnPage); @@ -52,12 +53,12 @@ public ToolResult getTableSchema( return ToolResult.success( semanticMergeService.getTableSchema(tableName, resolvedPage, resolvedPageSize)); } catch (IllegalStateException e) { - return ToolResult.error("数据源解析失败", e.getMessage()); + return ToolResult.error("Data source parsing failed", e.getMessage()); } catch (IllegalArgumentException e) { - return ToolResult.error("参数错误", e.getMessage()); + return ToolResult.error("Invalid arguments", e.getMessage()); } catch (RuntimeException e) { - log.error("获取表 {} 的 Schema 失败: {}", tableName, e.getMessage(), e); - return ToolResult.error("获取 Schema 失败", e.getMessage()); + log.error("Failed to get schema for table {}: {}", tableName, e.getMessage(), e); + return ToolResult.error("Failed to retrieve schema", e.getMessage()); } } } diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java index 537aaa5..6839c68 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java @@ -34,26 +34,22 @@ public class GetTablesTool implements MarkAgentTool { private final SemanticMergeService semanticMergeService; - @Tool( - name = "get_tables", - description = - "获取可见的表信息,包含表名、领域、描述以及该表与其他表的逻辑关系。" - + "返回 success/data/error 包装结构,data 包含分页的表项。") + @Tool(name = "get_tables", description = "获取数据库中的表信息,包括表名和表描述") public ToolResult> getTables( - @ToolParam(name = "page", description = "可选页码,默认为1") Integer page, - @ToolParam(name = "page_size", description = "可选每页大小,默认20,最大100") Integer pageSize) { + @ToolParam(name = "page", description = "Optional page number, default is 1") Integer page, + @ToolParam(name = "page_size", description = "Optional page size, default is 20, maximum is 100") Integer pageSize) { try { int resolvedPage = PageResponse.resolvePage(page); int resolvedPageSize = PageResponse.resolvePageSize(pageSize); return ToolResult.success( semanticMergeService.getVisibleTablePromptPage(resolvedPage, resolvedPageSize)); } catch (IllegalStateException e) { - return ToolResult.error("数据源解析失败", e.getMessage()); + return ToolResult.error("Data source parsing failed", e.getMessage()); } catch (IllegalArgumentException e) { - return ToolResult.error("参数错误", e.getMessage()); + return ToolResult.error("Invalid arguments", e.getMessage()); } catch (RuntimeException e) { - log.error("获取可见表失败: {}", e.getMessage(), e); - return ToolResult.error("获取表失败", e.getMessage()); + log.error("Failed to retrieve visible tables: {}", e.getMessage(), e); + return ToolResult.error("Failed to retrieve tables", e.getMessage()); } } } From 39598d5fdcda71c2467952b143625aa5840bf955 Mon Sep 17 00:00:00 2001 From: mengnankkkk Date: Wed, 3 Jun 2026 14:22:47 +0800 Subject: [PATCH 9/9] fix: ci --- .../malonetalk/agent/tools/GetTableSchemaTool.java | 10 ++++++++-- .../github/malonetalk/agent/tools/GetTablesTool.java | 8 ++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java index 5578fad..85f1580 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java @@ -44,8 +44,14 @@ public class GetTableSchemaTool implements MarkAgentTool { public ToolResult getTableSchema( @ToolParam(name = "table_name", description = "The table name to query schema for") String tableName, - @ToolParam(name = "column_page", description = "Optional column page number, default is 1") Integer columnPage, - @ToolParam(name = "column_page_size", description = "Optional column page size, default is 20, maximum is 100") + @ToolParam( + name = "column_page", + description = "Optional column page number, default is 1") + Integer columnPage, + @ToolParam( + name = "column_page_size", + description = + "Optional column page size, default is 20, maximum is 100") Integer columnPageSize) { try { int resolvedPage = PageResponse.resolvePage(columnPage); diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java index 6839c68..dba4b7a 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java @@ -36,8 +36,12 @@ public class GetTablesTool implements MarkAgentTool { @Tool(name = "get_tables", description = "获取数据库中的表信息,包括表名和表描述") public ToolResult> getTables( - @ToolParam(name = "page", description = "Optional page number, default is 1") Integer page, - @ToolParam(name = "page_size", description = "Optional page size, default is 20, maximum is 100") Integer pageSize) { + @ToolParam(name = "page", description = "Optional page number, default is 1") + Integer page, + @ToolParam( + name = "page_size", + description = "Optional page size, default is 20, maximum is 100") + Integer pageSize) { try { int resolvedPage = PageResponse.resolvePage(page); int resolvedPageSize = PageResponse.resolvePageSize(pageSize);