Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
885b33a
feat: add sync service in semantic
mengnankkkk Jul 4, 2026
5413dee
fix: add bug fix
mengnankkkk Jul 8, 2026
7f8e028
fix: ci
mengnankkkk Jul 8, 2026
a21a621
fix: add remove
mengnankkkk Jul 9, 2026
138b90b
fix: add now in mapper
mengnankkkk Jul 11, 2026
0fbeb37
fix: add turn
mengnankkkk Jul 11, 2026
5638aef
fix: add sync builder
mengnankkkk Jul 11, 2026
02e4c37
feat: replace with public class
mengnankkkk Jul 11, 2026
cee9f2c
fix: add sync fix
mengnankkkk Jul 11, 2026
23b4c59
Merge branch 'main' into feature/add_sync_datasource
VLSMB Jul 11, 2026
d83b921
fix: fix CR
mengnankkkk Jul 16, 2026
482da21
fix: add batch query
mengnankkkk Jul 16, 2026
5a9bcbe
refactor: use Lombok @Builder for SyncTableResult
lzq986 Jul 18, 2026
9511773
Merge branch 'main' into feature/add_sync_datasource
mengnankkkk Jul 18, 2026
164696a
Merge remote-tracking branch 'origin/feature/add_sync_datasource' int…
lzq986 Jul 18, 2026
6403e9f
refactor: require contextual semantic missing-field errors
mengnankkkk Jul 18, 2026
ab6e42f
fix: guard against null/empty tableNames in SchemaReader
lzq986 Jul 18, 2026
6ec4b51
Merge remote-tracking branch 'origin/feature/add_sync_datasource' int…
lzq986 Jul 18, 2026
b7f1408
refactor: remove SemanticUtils.objectKey
mengnankkkk Jul 18, 2026
f8a1113
Merge branch 'feature/add_sync_datasource' of github.com:MaloneTalk/D…
mengnankkkk Jul 18, 2026
5f73b54
fix: shorten refresh physical status transactions
mengnankkkk Jul 18, 2026
fafef09
refactor: make SemanticAvailabilityHelper fail-fast on null
lzq986 Jul 18, 2026
c4f625d
docs: clarify semantics of ColumnSemanticResponse.effective
lzq986 Jul 18, 2026
80f682b
refactor: use Lombok @Builder for RelationWorkspace responses
lzq986 Jul 18, 2026
9190e40
refactor: simplify sync diff returns and use positive availability pr…
lzq986 Jul 18, 2026
654e38e
refactor: rename tableMarkedMissing to tableMarkedAsMissing
lzq986 Jul 18, 2026
baec28b
refactor: extract enums, remove dead code, fix config
VLSMB Jul 18, 2026
e74cabb
perf: batch sync physical schema metadata
mengnankkkk Jul 19, 2026
e978835
fix: replace gettables
mengnankkkk Jul 19, 2026
ae1caf0
refactor: simplify semantic schema sync implementation
mengnankkkk Jul 19, 2026
8309e5f
fix: fix ci
VLSMB Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -63,6 +67,36 @@ public List<PhysicalColumnInfo> getTableSchema(Datasource datasource, String tab
}
}

public Map<String, Set<String>> getTableColumnNames(
Datasource datasource, Collection<String> tableNames) {
Map<String, Set<String>> columnNamesByTable = new LinkedHashMap<>();
for (String tableName : tableNames) {
columnNamesByTable.putIfAbsent(tableName, new LinkedHashSet<>());
}
if (columnNamesByTable.isEmpty()) {
return columnNamesByTable;
}

javax.sql.DataSource ds = dynamicDataSourceManager.getOrCreateDataSource(datasource);

try (Connection conn = ds.getConnection()) {
DatabaseMetaData metaData = conn.getMetaData();
try (ResultSet rs =
metaData.getColumns(conn.getCatalog(), conn.getSchema(), "%", null)) {
while (rs.next()) {
Set<String> columnNames = columnNamesByTable.get(rs.getString("TABLE_NAME"));
if (columnNames != null) {
columnNames.add(rs.getString("COLUMN_NAME"));
}
}
}
return columnNamesByTable;
} catch (SQLException e) {
log.error("Failed to read column names for tables: {}", e.getMessage(), e);
throw new SchemaReadException("Failed to read column names: " + e.getMessage(), e);
}
}

private List<PhysicalTableInfo> getTables(Connection conn) throws SQLException {
List<PhysicalTableInfo> tables = new ArrayList<>();
DatabaseMetaData metaData = conn.getMetaData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package io.github.malonetalk.agent.tools;

import io.agentscope.core.tool.Tool;
import io.github.malonetalk.service.DomainService;
import io.github.malonetalk.service.semantic.DomainService;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import io.github.malonetalk.dto.DomainUpdateRequest;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.entity.DomainInfo;
import io.github.malonetalk.service.DomainService;
import io.github.malonetalk.service.semantic.DomainService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.semantic.RelationWorkspacePageQuery;
import io.github.malonetalk.dto.semantic.RelationWorkspaceResponse;
import io.github.malonetalk.service.semantic.relation.RelationSemanticService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/semantic/tables/relations/workspace")
@RequiredArgsConstructor
public class TableRelationWorkspaceController {

private final RelationSemanticService relationSemanticService;

@GetMapping
public Result<RelationWorkspaceResponse> getWorkspace(@Valid RelationWorkspacePageQuery query) {
return Result.success(relationSemanticService.getRelationWorkspace(query));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.dto.semantic.PhysicalTableCandidatePageQuery;
import io.github.malonetalk.dto.semantic.PhysicalTableCandidateResponse;
import io.github.malonetalk.dto.semantic.RefreshPhysicalStatusRequest;
import io.github.malonetalk.dto.semantic.SyncTableSemanticsRequest;
import io.github.malonetalk.dto.semantic.SyncTableSemanticsResponse;
import io.github.malonetalk.service.semantic.sync.SemanticSyncService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/semantic/tables/sync")
@RequiredArgsConstructor
public class TableSemanticSyncController {

private final SemanticSyncService semanticSyncService;

@GetMapping("/candidates")
public Result<PageResponse<PhysicalTableCandidateResponse>> getPhysicalTableCandidates(
@Valid PhysicalTableCandidatePageQuery query) {
return Result.success(semanticSyncService.getPhysicalTableCandidates(query));
}

@PostMapping
public Result<SyncTableSemanticsResponse> syncTables(
@Valid @RequestBody SyncTableSemanticsRequest request) {
return Result.success(semanticSyncService.syncTables(request));
}

@PostMapping("/physical-status")
public Result<SyncTableSemanticsResponse> refreshPhysicalStatus(
@Valid @RequestBody RefreshPhysicalStatusRequest request) {
return Result.success(semanticSyncService.refreshPhysicalStatus(request));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@
import io.github.malonetalk.dto.prompt.TableRelationPromptResponse;
import io.github.malonetalk.entity.ColumnInfo;
import io.github.malonetalk.entity.TableInfo;
import io.github.malonetalk.service.semantic.SemanticAvailabilityHelper;
import io.github.malonetalk.utils.SemanticUtils;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

/** 物理层/语义层 → Agent Prompt DTO 的统一转换器,集中管理所有面向 LLM 的 DTO 映射逻辑。 */
public final class PromptConverter {
Expand All @@ -40,16 +39,16 @@ private PromptConverter() {}
public static ColumnPromptResponse mapColumnPrompt(
PhysicalColumnInfo physicalColumn, Map<String, ColumnInfo> semanticByName) {
ColumnInfo semanticColumn =
semanticByName.get(physicalColumn.columnName().toLowerCase(Locale.ROOT));
if (semanticColumn != null && !Boolean.TRUE.equals(semanticColumn.getIsVisible())) {
semanticByName.get(SemanticUtils.objectKey(physicalColumn.columnName()));
if (!SemanticAvailabilityHelper.isColumnAvailable(
semanticColumn, SemanticAvailabilityHelper.UsageLevel.AI_PROMPT)) {
return null;
}

String description =
Optional.ofNullable(semanticColumn)
.map(ColumnInfo::getColumnDescription)
.map(SemanticUtils::trimToNull)
.orElseGet(() -> SemanticUtils.trimToNull(physicalColumn.remarks()));
SemanticUtils.firstNonBlank(
semanticColumn == null ? null : semanticColumn.getColumnDescription(),
physicalColumn.remarks());

StringBuilder typeBuilder = new StringBuilder(physicalColumn.typeName());
if (physicalColumn.columnSize() > 0) {
Expand All @@ -72,8 +71,9 @@ public static TablePromptResponse mapTablePrompt(
Map<String, TableInfo> semanticByName,
List<TableRelationPromptResponse> resolvedRelations) {
TableInfo semanticTable =
semanticByName.get(physicalTable.tableName().toLowerCase(Locale.ROOT));
if (semanticTable != null && !Boolean.TRUE.equals(semanticTable.getIsVisible())) {
semanticByName.get(SemanticUtils.objectKey(physicalTable.tableName()));
if (!SemanticAvailabilityHelper.isTableAvailable(
semanticTable, SemanticAvailabilityHelper.UsageLevel.AI_PROMPT)) {
return null;
}

Expand All @@ -93,9 +93,8 @@ private static String resolveDomain(TableInfo semanticTable) {

private static String resolveDescription(
PhysicalTableInfo physicalTable, TableInfo semanticTable) {
return Optional.ofNullable(semanticTable)
.map(TableInfo::getTableDescription)
.map(SemanticUtils::trimToNull)
.orElseGet(() -> SemanticUtils.trimToNull(physicalTable.remarks()));
return SemanticUtils.firstNonBlank(
semanticTable == null ? null : semanticTable.getTableDescription(),
physicalTable.remarks());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
import io.github.malonetalk.common.SemanticConstants;
import io.github.malonetalk.dto.semantic.ColumnSemanticResponse;
import io.github.malonetalk.dto.semantic.LogicalTableRelationResponse;
import io.github.malonetalk.dto.semantic.RelationWorkspaceColumnResponse;
import io.github.malonetalk.dto.semantic.RelationWorkspaceTableResponse;
import io.github.malonetalk.dto.semantic.TableSemanticResponse;
import io.github.malonetalk.entity.ColumnInfo;
import io.github.malonetalk.entity.LogicalTableRelation;
import io.github.malonetalk.entity.TableInfo;
import io.github.malonetalk.enums.LogicalTableRelationType;
import io.github.malonetalk.service.semantic.SemanticAvailabilityHelper;
import io.github.malonetalk.service.semantic.relation.LogicalTableRelationHelper;
import io.github.malonetalk.utils.SemanticUtils;
import java.util.List;
Expand All @@ -39,25 +42,41 @@ public class SemanticConverter {

public TableSemanticResponse toResponse(TableInfo tableInfo) {
Boolean isVisible = tableInfo.getIsVisible();
boolean hasPhysicalTable = SemanticAvailabilityHelper.hasPhysicalTable(tableInfo);
return TableSemanticResponse.builder()
.id(tableInfo.getId())
.tableName(tableInfo.getTableName())
.domain(SemanticUtils.normalizeDomain(tableInfo.getDomain()))
.physicalTableDescription(
SemanticUtils.trimToNull(tableInfo.getPhysicalTableDescription()))
.tableDescription(SemanticUtils.trimToNull(tableInfo.getTableDescription()))
.isVisible(isVisible)
.hasPhysicalTable(true)
.hasPhysicalTable(hasPhysicalTable)
.invalidReason(
SemanticAvailabilityHelper.tableInvalidReason(
tableInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION))
.updateTime(tableInfo.getUpdateTime())
.build();
}

public ColumnSemanticResponse toResponse(ColumnInfo columnInfo) {
boolean hasPhysicalColumn = SemanticAvailabilityHelper.hasPhysicalColumn(columnInfo);
return ColumnSemanticResponse.builder()
.id(columnInfo.getId())
.columnName(columnInfo.getColumnName())
.physicalColumnDescription(
SemanticUtils.trimToNull(columnInfo.getPhysicalColumnDescription()))
.columnDescription(columnInfo.getColumnDescription())
.typeName(SemanticUtils.trimToNull(columnInfo.getTypeName()))
.primaryKey(columnInfo.getPrimaryKey())
.isVisible(columnInfo.getIsVisible())
.hasPhysicalColumn(true)
.effective(Boolean.TRUE.equals(columnInfo.getIsVisible()))
.hasPhysicalColumn(hasPhysicalColumn)
.effective(
SemanticAvailabilityHelper.isColumnAvailable(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION))
.invalidReason(
SemanticAvailabilityHelper.columnInvalidReason(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION))
.updateTime(columnInfo.getUpdateTime())
.build();
}
Expand Down Expand Up @@ -91,4 +110,32 @@ public LogicalTableRelationResponse toResponse(LogicalTableRelation relation) {
.updateTime(relation.getUpdateTime())
.build();
}

public RelationWorkspaceTableResponse toWorkspaceTable(
TableInfo tableInfo, List<ColumnInfo> columns) {
return new RelationWorkspaceTableResponse(
tableInfo.getTableName(),
SemanticUtils.normalizeDomain(tableInfo.getDomain()),
SemanticUtils.firstNonBlank(
tableInfo.getTableDescription(), tableInfo.getPhysicalTableDescription()),
SemanticAvailabilityHelper.isTableAvailable(
tableInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION),
SemanticAvailabilityHelper.tableInvalidReason(
tableInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION),
columns.stream().map(this::toWorkspaceColumn).toList());
}

public RelationWorkspaceColumnResponse toWorkspaceColumn(ColumnInfo columnInfo) {
return new RelationWorkspaceColumnResponse(
columnInfo.getColumnName(),
SemanticUtils.firstNonBlank(
columnInfo.getColumnDescription(),
columnInfo.getPhysicalColumnDescription()),
SemanticUtils.trimToNull(columnInfo.getTypeName()),
columnInfo.getPrimaryKey(),
SemanticAvailabilityHelper.isColumnAvailable(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION),
SemanticAvailabilityHelper.columnInvalidReason(
columnInfo, SemanticAvailabilityHelper.UsageLevel.USER_OPERATION));
}
}
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto.semantic;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public record PhysicalTableCandidatePageQuery(
@NotNull(message = "datasourceId 不能为空") Integer datasourceId,
@Min(value = 1, message = "page 不能小于 1") Integer page,
@Min(value = 1, message = "pageSize 不能小于 1") Integer pageSize,
String keyword,
String sortOrder) {}
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto.semantic;

public record PhysicalTableCandidateResponse(
String tableName, String physicalTableDescription, Boolean synced) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto.semantic;

import jakarta.validation.constraints.NotNull;

public record RefreshPhysicalStatusRequest(
@NotNull(message = "datasourceId 不能为空") Integer datasourceId) {}
Loading
Loading