You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/07/01 12:00:25 UTC
(shardingsphere) branch master updated: Refactor MCP workflow support duplication (#38974)
This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 9e60e6a3c4f Refactor MCP workflow support duplication (#38974)
9e60e6a3c4f is described below
commit 9e60e6a3c4f80a53e6fa60384e8cfbfb83da04e1
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Wed Jul 1 20:00:19 2026 +0800
Refactor MCP workflow support duplication (#38974)
* Fix documentation accuracy and consistency
- align DistSQL syntax docs, return columns, and examples with current source
- normalize SQL and server error code documentation
- fix MCP resources, management metadata docs, links, versions, and typos
* Fix documentation accuracy and consistency
- align DistSQL syntax docs, return columns, and examples with current source
- normalize SQL and server error code documentation
- fix MCP resources, management metadata docs, links, versions, and typos
* Refactor MCP workflow support duplication
Centralize repeated workflow validation, identifier checks, DistSQL rule query fallback, and MCP error/recovery mapping while preserving existing behavior. Reuse shared helpers across MCP feature planning/validation services and simplify duplicated MCP E2E test setup helpers.
---
.../mcp/core/protocol/error/MCPErrorConverter.java | 85 +++----
.../protocol/error/MCPRecoveryPayloadFactory.java | 96 ++++----
.../service/BroadcastWorkflowPlanningService.java | 23 +-
.../BroadcastWorkflowValidationService.java | 8 +-
.../tool/service/EncryptRuleInspectionService.java | 17 +-
.../service/EncryptWorkflowValidationService.java | 8 +-
.../tool/service/MaskRuleInspectionService.java | 15 +-
.../service/MaskWorkflowValidationService.java | 8 +-
...dwriteSplittingRuleWorkflowPlanningService.java | 26 +--
...riteSplittingRuleWorkflowValidationService.java | 8 +-
...riteSplittingStatusWorkflowPlanningService.java | 15 +-
...teSplittingStatusWorkflowValidationService.java | 8 +-
.../service/ShadowWorkflowPlanningService.java | 46 ++--
.../service/ShadowWorkflowValidationService.java | 8 +-
.../service/ShardingWorkflowValidationService.java | 8 +-
.../service/WorkflowDistSQLQueryUtils.java | 22 ++
.../WorkflowGuidanceResourceHintProvider.java | 259 ++++++++++-----------
.../service/WorkflowIntentResolverSupport.java | 64 ++---
.../workflow/service/WorkflowPlanningSupport.java | 71 +++++-
.../service/WorkflowValidationSupport.java | 21 ++
.../service/WorkflowDistSQLQueryUtilsTest.java | 33 +++
.../service/WorkflowPlanningSupportTest.java | 18 ++
.../service/WorkflowValidationSupportTest.java | 32 +++
.../AbstractHttpProgrammaticRuntimeE2ETest.java | 15 ++
.../HttpTransportApprovalSafetyE2ETest.java | 15 --
.../HttpTransportCompletionE2ETest.java | 14 --
.../support/runtime/MySQLRuntimeTestSupport.java | 29 ++-
27 files changed, 493 insertions(+), 479 deletions(-)
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java
index 8c7fad6d73c..4aa11fb52b9 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java
@@ -36,6 +36,7 @@ import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLTimeoutException;
+import java.util.List;
import java.util.Objects;
/**
@@ -44,6 +45,26 @@ import java.util.Objects;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MCPErrorConverter {
+ private static final List<ErrorMapping> ERROR_MAPPINGS = List.of(
+ new ErrorMapping(UnsupportedToolException.class, "Unsupported tool."),
+ new ErrorMapping(UnsupportedResourceUriException.class, "Unsupported resource URI."),
+ new ErrorMapping(MCPInvalidRequestException.class, "Invalid request."),
+ new ErrorMapping(MCPNotFoundException.class, "MCP operation not found."),
+ new ErrorMapping(MCPUnsupportedException.class, "Unsupported MCP operation."),
+ new ErrorMapping(MCPTimeoutException.class, "MCP operation timeout."),
+ new ErrorMapping(MCPTransactionStateException.class, "MCP transaction operation failed."),
+ new ErrorMapping(MCPQueryFailedException.class, "MCP query failed."),
+ new ErrorMapping(MCPToolCallLimitExceededException.class, "MCP tool call limit exceeded."),
+ new ErrorMapping(MCPUnavailableException.class, "Service is temporarily unavailable."),
+ new ErrorMapping(RuntimeDatabaseConnectionException.class, "Runtime database connection failed."),
+ new ErrorMapping(SQLSyntaxErrorException.class, "Invalid request."),
+ new ErrorMapping(SQLTimeoutException.class, "MCP operation timeout."),
+ new ErrorMapping(SQLFeatureNotSupportedException.class, "Unsupported MCP operation."),
+ new ErrorMapping(UnsupportedOperationException.class, "Unsupported MCP operation."),
+ new ErrorMapping(SQLException.class, "MCP query failed."),
+ new ErrorMapping(IllegalArgumentException.class, "Invalid request."),
+ new ErrorMapping(IllegalStateException.class, "MCP operation failed."));
+
/**
* Convert throwable to MCP error.
*
@@ -51,59 +72,10 @@ public final class MCPErrorConverter {
* @return MCP error
*/
public static MCPErrorResponse convert(final Throwable cause) {
- if (cause instanceof UnsupportedToolException) {
- return createError(cause, "Unsupported tool.");
- }
- if (cause instanceof UnsupportedResourceUriException) {
- return createError(cause, "Unsupported resource URI.");
- }
- if (cause instanceof MCPInvalidRequestException) {
- return createError(cause, "Invalid request.");
- }
- if (cause instanceof MCPNotFoundException) {
- return createError(cause, "MCP operation not found.");
- }
- if (cause instanceof MCPUnsupportedException) {
- return createError(cause, "Unsupported MCP operation.");
- }
- if (cause instanceof MCPTimeoutException) {
- return createError(cause, "MCP operation timeout.");
- }
- if (cause instanceof MCPTransactionStateException) {
- return createError(cause, "MCP transaction operation failed.");
- }
- if (cause instanceof MCPQueryFailedException) {
- return createError(cause, "MCP query failed.");
- }
- if (cause instanceof MCPToolCallLimitExceededException) {
- return createError(cause, "MCP tool call limit exceeded.");
- }
- if (cause instanceof MCPUnavailableException) {
- return createError(cause, "Service is temporarily unavailable.");
- }
- if (cause instanceof RuntimeDatabaseConnectionException) {
- return createError(cause, "Runtime database connection failed.");
- }
- if (cause instanceof SQLSyntaxErrorException) {
- return createError(cause, "Invalid request.");
- }
- if (cause instanceof SQLTimeoutException) {
- return createError(cause, "MCP operation timeout.");
- }
- if (cause instanceof SQLFeatureNotSupportedException) {
- return createError(cause, "Unsupported MCP operation.");
- }
- if (cause instanceof UnsupportedOperationException) {
- return createError(cause, "Unsupported MCP operation.");
- }
- if (cause instanceof SQLException) {
- return createError(cause, "MCP query failed.");
- }
- if (cause instanceof IllegalArgumentException) {
- return createError(cause, "Invalid request.");
- }
- if (cause instanceof IllegalStateException) {
- return createError(cause, "MCP operation failed.");
+ for (ErrorMapping each : ERROR_MAPPINGS) {
+ if (each.matches(cause)) {
+ return createError(cause, each.defaultMessage());
+ }
}
return createError(cause, "Service is temporarily unavailable.");
}
@@ -112,4 +84,11 @@ public final class MCPErrorConverter {
String message = MCPQueryRecoveryPayloadFactory.isQueryFailure(cause) ? defaultMessage : Objects.toString(cause.getMessage(), defaultMessage).trim();
return new MCPErrorResponse(message, MCPRecoveryPayloadFactory.create(cause));
}
+
+ private record ErrorMapping(Class<? extends Throwable> causeType, String defaultMessage) {
+
+ private boolean matches(final Throwable cause) {
+ return causeType.isInstance(cause);
+ }
+ }
}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java
index 42f969eec99..d7ee80899a7 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java
@@ -37,7 +37,9 @@ import org.apache.shardingsphere.mcp.core.tool.handler.execute.MetadataIntrospec
import org.apache.shardingsphere.mcp.core.tool.handler.execute.SQLToolMismatchException;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConnectionException;
+import java.util.List;
import java.util.Map;
+import java.util.function.Function;
/**
* MCP recovery payload factory.
@@ -45,61 +47,55 @@ import java.util.Map;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
final class MCPRecoveryPayloadFactory {
+ private static final List<RecoveryMapping> RECOVERY_MAPPINGS = List.of(
+ new RecoveryMapping(SQLToolMismatchException.class, cause -> MCPSQLRecoveryPayloadFactory.createSQLToolMismatchRecovery((SQLToolMismatchException) cause)),
+ new RecoveryMapping(MetadataIntrospectionSQLStatementException.class,
+ cause -> MCPSQLRecoveryPayloadFactory.createMetadataIntrospectionSQLRecovery((MetadataIntrospectionSQLStatementException) cause)),
+ new RecoveryMapping(MCPMultipleSQLStatementsException.class, cause -> MCPSQLRecoveryPayloadFactory.createMultipleStatementsRecovery()),
+ new RecoveryMapping(MCPUnsupportedSQLStatementException.class, cause -> MCPSQLRecoveryPayloadFactory.createUnsupportedStatementRecovery()),
+ new RecoveryMapping(MCPBannedSQLStatementException.class, cause -> MCPSQLRecoveryPayloadFactory.createBannedStatementRecovery()),
+ new RecoveryMapping(MCPExecutionModeRequiredException.class,
+ cause -> MCPWorkflowRecoveryPayloadFactory.createMissingExecutionModeRecovery((MCPExecutionModeRequiredException) cause)),
+ new RecoveryMapping(MCPInvalidExecutionModeException.class,
+ cause -> MCPWorkflowRecoveryPayloadFactory.createInvalidExecutionModeRecovery((MCPInvalidExecutionModeException) cause)),
+ new RecoveryMapping(MCPInvalidApprovedStepsException.class,
+ cause -> MCPWorkflowRecoveryPayloadFactory.createInvalidApprovedStepsRecovery((MCPInvalidApprovedStepsException) cause)),
+ new RecoveryMapping(MCPWorkflowStateException.class, cause -> MCPWorkflowRecoveryPayloadFactory.createWorkflowStateRecovery((MCPWorkflowStateException) cause)),
+ new RecoveryMapping(UnsupportedToolException.class, cause -> MCPBasicRecoveryPayloadFactory.createUnsupportedToolRecovery(((UnsupportedToolException) cause).getToolName())),
+ new RecoveryMapping(UnsupportedResourceUriException.class,
+ cause -> MCPBasicRecoveryPayloadFactory.createUnsupportedResourceRecovery(((UnsupportedResourceUriException) cause).getResourceUri())),
+ new RecoveryMapping(RuntimeDatabaseConnectionException.class,
+ cause -> MCPBasicRecoveryPayloadFactory.createRuntimeDatabaseConnectionRecovery((RuntimeDatabaseConnectionException) cause)),
+ new RecoveryMapping(MCPToolCallLimitExceededException.class,
+ cause -> MCPBasicRecoveryPayloadFactory.createToolCallLimitRecovery((MCPToolCallLimitExceededException) cause)),
+ new RecoveryMapping(MCPInvalidToolArgumentException.class, cause -> MCPBasicRecoveryPayloadFactory.createInvalidToolArgumentRecovery((MCPInvalidToolArgumentException) cause)),
+ new RecoveryMapping(MCPToolArgumentContractViolationException.class,
+ cause -> MCPBasicRecoveryPayloadFactory.createToolArgumentContractViolationRecovery((MCPToolArgumentContractViolationException) cause)),
+ new RecoveryMapping(MCPMissingToolArgumentException.class,
+ cause -> MCPBasicRecoveryPayloadFactory.createMissingArgumentRecovery(((MCPMissingToolArgumentException) cause).getArgumentName())),
+ new RecoveryMapping(MCPInvalidMetadataObjectTypesException.class,
+ cause -> MCPBasicRecoveryPayloadFactory.createInvalidObjectTypesRecovery((MCPInvalidMetadataObjectTypesException) cause)));
+
static Map<String, Object> create(final Throwable cause) {
- if (cause instanceof SQLToolMismatchException) {
- return MCPSQLRecoveryPayloadFactory.createSQLToolMismatchRecovery((SQLToolMismatchException) cause);
- }
- if (cause instanceof MetadataIntrospectionSQLStatementException) {
- return MCPSQLRecoveryPayloadFactory.createMetadataIntrospectionSQLRecovery((MetadataIntrospectionSQLStatementException) cause);
- }
- if (cause instanceof MCPMultipleSQLStatementsException) {
- return MCPSQLRecoveryPayloadFactory.createMultipleStatementsRecovery();
- }
- if (cause instanceof MCPUnsupportedSQLStatementException) {
- return MCPSQLRecoveryPayloadFactory.createUnsupportedStatementRecovery();
- }
- if (cause instanceof MCPBannedSQLStatementException) {
- return MCPSQLRecoveryPayloadFactory.createBannedStatementRecovery();
- }
- if (cause instanceof MCPExecutionModeRequiredException) {
- return MCPWorkflowRecoveryPayloadFactory.createMissingExecutionModeRecovery((MCPExecutionModeRequiredException) cause);
- }
- if (cause instanceof MCPInvalidExecutionModeException) {
- return MCPWorkflowRecoveryPayloadFactory.createInvalidExecutionModeRecovery((MCPInvalidExecutionModeException) cause);
- }
- if (cause instanceof MCPInvalidApprovedStepsException) {
- return MCPWorkflowRecoveryPayloadFactory.createInvalidApprovedStepsRecovery((MCPInvalidApprovedStepsException) cause);
- }
- if (cause instanceof MCPWorkflowStateException) {
- return MCPWorkflowRecoveryPayloadFactory.createWorkflowStateRecovery((MCPWorkflowStateException) cause);
- }
- if (cause instanceof UnsupportedToolException) {
- return MCPBasicRecoveryPayloadFactory.createUnsupportedToolRecovery(((UnsupportedToolException) cause).getToolName());
- }
- if (cause instanceof UnsupportedResourceUriException) {
- return MCPBasicRecoveryPayloadFactory.createUnsupportedResourceRecovery(((UnsupportedResourceUriException) cause).getResourceUri());
- }
- if (cause instanceof RuntimeDatabaseConnectionException) {
- return MCPBasicRecoveryPayloadFactory.createRuntimeDatabaseConnectionRecovery((RuntimeDatabaseConnectionException) cause);
- }
- if (cause instanceof MCPToolCallLimitExceededException) {
- return MCPBasicRecoveryPayloadFactory.createToolCallLimitRecovery((MCPToolCallLimitExceededException) cause);
- }
- if (cause instanceof MCPInvalidToolArgumentException) {
- return MCPBasicRecoveryPayloadFactory.createInvalidToolArgumentRecovery((MCPInvalidToolArgumentException) cause);
- }
- if (cause instanceof MCPToolArgumentContractViolationException) {
- return MCPBasicRecoveryPayloadFactory.createToolArgumentContractViolationRecovery((MCPToolArgumentContractViolationException) cause);
- }
- if (cause instanceof MCPMissingToolArgumentException) {
- return MCPBasicRecoveryPayloadFactory.createMissingArgumentRecovery(((MCPMissingToolArgumentException) cause).getArgumentName());
- }
- if (cause instanceof MCPInvalidMetadataObjectTypesException) {
- return MCPBasicRecoveryPayloadFactory.createInvalidObjectTypesRecovery((MCPInvalidMetadataObjectTypesException) cause);
+ for (RecoveryMapping each : RECOVERY_MAPPINGS) {
+ if (each.matches(cause)) {
+ return each.create(cause);
+ }
}
if (MCPQueryRecoveryPayloadFactory.isQueryFailure(cause)) {
return MCPQueryRecoveryPayloadFactory.create(cause);
}
return Map.of();
}
+
+ private record RecoveryMapping(Class<? extends Throwable> causeType, Function<Throwable, Map<String, Object>> payloadFactory) {
+
+ private boolean matches(final Throwable cause) {
+ return causeType.isInstance(cause);
+ }
+
+ private Map<String, Object> create(final Throwable cause) {
+ return payloadFactory.apply(cause);
+ }
+ }
}
diff --git a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
index 2b5e09b8941..0586b2d20d8 100644
--- a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
+++ b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
@@ -30,7 +30,6 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningSu
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRuleValueUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -113,7 +112,8 @@ public final class BroadcastWorkflowPlanningService {
snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
return false;
}
- if (!ensureSupportedIdentifier("database", request.getDatabase(), snapshot) || !ensureSupportedIdentifiers("tables", request.getTargetTables(), snapshot)) {
+ if (!planningSupport.ensureSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "discovering")
+ || !planningSupport.ensureSupportedIdentifiers("tables", request.getTargetTables(), snapshot, "discovering")) {
snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
return false;
}
@@ -128,25 +128,6 @@ public final class BroadcastWorkflowPlanningService {
return true;
}
- private boolean ensureSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot) {
- for (String each : identifiers) {
- if (!ensureSupportedIdentifier(fieldName, each, snapshot)) {
- return false;
- }
- }
- return true;
- }
-
- private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
- if (WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
- return true;
- }
- snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "discovering",
- String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
- "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
- return false;
- }
-
private boolean ensureLifecycleState(final ClarifiedIntent clarifiedIntent, final BroadcastWorkflowRequest request, final List<Map<String, Object>> broadcastRules,
final WorkflowContextSnapshot snapshot, final String databaseType) {
boolean dropWorkflow = WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(clarifiedIntent.getOperationType());
diff --git a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java
index 630dd312dc9..d74ce8be30c 100644
--- a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java
+++ b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java
@@ -62,13 +62,7 @@ public final class BroadcastWorkflowValidationService implements MCPWorkflowRunt
public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
final WorkflowContextSnapshot snapshot) {
- Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
- if (!rejectedResponse.isEmpty()) {
- return rejectedResponse;
- }
- ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
- snapshot.setValidationReport(validationReport);
- return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
}
@Override
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
index 9be1b45ba74..e58cd310301 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
@@ -17,8 +17,8 @@
package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
-import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
import org.apache.shardingsphere.mcp.feature.encrypt.EncryptFeatureDefinition;
+import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowDistSQLQueryUtils;
@@ -44,7 +44,7 @@ public final class EncryptRuleInspectionService {
* @return encrypt rules
*/
public List<Map<String, Object>> queryEncryptRules(final MCPFeatureQueryFacade queryFacade, final String databaseName) {
- return queryRuleRows(queryFacade, databaseName, String.format("SHOW ENCRYPT RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
+ return WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, databaseName, String.format("SHOW ENCRYPT RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
}
/**
@@ -56,7 +56,7 @@ public final class EncryptRuleInspectionService {
* @return encrypt rules
*/
public List<Map<String, Object>> queryEncryptRules(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String tableName) {
- return queryRuleRows(
+ return WorkflowDistSQLQueryUtils.queryRuleRows(
queryFacade, databaseName,
String.format("SHOW ENCRYPT TABLE RULE %s FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(tableName), WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
}
@@ -87,17 +87,6 @@ public final class EncryptRuleInspectionService {
return result;
}
- private List<Map<String, Object>> queryRuleRows(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String sql) {
- try {
- return queryFacade.query(databaseName, "", sql);
- } catch (final MCPQueryFailedException ex) {
- if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
- return List.of();
- }
- throw ex;
- }
- }
-
private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
try {
return queryFacade.queryWithAnyDatabase("SHOW ENCRYPT ALGORITHM PLUGINS");
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
index c7417257467..3ecc86e065c 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
@@ -72,13 +72,7 @@ public final class EncryptWorkflowValidationService implements MCPWorkflowRuntim
public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
final WorkflowContextSnapshot snapshot) {
- Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
- if (!rejectedResponse.isEmpty()) {
- return rejectedResponse;
- }
- ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
- snapshot.setValidationReport(validationReport);
- return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
}
@Override
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
index 7de3d5926db..e924daa449e 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
@@ -45,7 +45,7 @@ public final class MaskRuleInspectionService {
* @return mask rules
*/
public List<Map<String, Object>> queryMaskRules(final MCPFeatureQueryFacade queryFacade, final String databaseName) {
- return queryRuleRows(queryFacade, databaseName, String.format("SHOW MASK RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
+ return WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, databaseName, String.format("SHOW MASK RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
}
/**
@@ -57,7 +57,7 @@ public final class MaskRuleInspectionService {
* @return mask rules
*/
public List<Map<String, Object>> queryMaskRules(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String tableName) {
- return queryRuleRows(
+ return WorkflowDistSQLQueryUtils.queryRuleRows(
queryFacade, databaseName, String.format("SHOW MASK RULE %s FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(tableName), WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
}
@@ -71,17 +71,6 @@ public final class MaskRuleInspectionService {
return decorateMaskAlgorithms(queryAlgorithmRows(queryFacade));
}
- private List<Map<String, Object>> queryRuleRows(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String sql) {
- try {
- return queryFacade.query(databaseName, "", sql);
- } catch (final MCPQueryFailedException ex) {
- if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
- return List.of();
- }
- throw ex;
- }
- }
-
private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
try {
return queryFacade.queryWithAnyDatabase("SHOW MASK ALGORITHM PLUGINS");
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
index ed1953d3904..8e01b46687d 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
@@ -70,13 +70,7 @@ public final class MaskWorkflowValidationService implements MCPWorkflowRuntimeHa
public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
final WorkflowContextSnapshot snapshot) {
- Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
- if (!rejectedResponse.isEmpty()) {
- return rejectedResponse;
- }
- ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
- snapshot.setValidationReport(validationReport);
- return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
}
@Override
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
index d71b0f52a84..7fc058b4759 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
@@ -128,9 +128,10 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
return false;
}
- if (!ensureSupportedIdentifier("database", request.getDatabase(), snapshot) || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.RULE_FIELD, request.getRuleName(), snapshot)
- || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, request.getWriteStorageUnit(), snapshot)
- || !ensureSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request.getReadStorageUnits(), snapshot)) {
+ if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "intaking")
+ || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()), snapshot, "intaking")
+ || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, List.of(request.getWriteStorageUnit()), snapshot, "intaking")
+ || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request.getReadStorageUnits(), snapshot, "intaking")) {
snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
return false;
}
@@ -167,25 +168,6 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
}
}
- private boolean ensureSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot) {
- for (String each : identifiers) {
- if (!ensureSupportedIdentifier(fieldName, each, snapshot)) {
- return false;
- }
- }
- return true;
- }
-
- private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
- if (identifier.isEmpty() || WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
- return true;
- }
- snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
- String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
- "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
- return false;
- }
-
private boolean ensureLifecycleState(final ClarifiedIntent clarifiedIntent, final ReadwriteSplittingRuleWorkflowRequest request, final List<Map<String, Object>> rules,
final WorkflowContextSnapshot snapshot, final String databaseType) {
boolean ruleExists = containsRule(rules, databaseType, request.getRuleName());
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java
index 114b490a422..565013675ae 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java
@@ -66,13 +66,7 @@ public final class ReadwriteSplittingRuleWorkflowValidationService implements MC
public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
final WorkflowContextSnapshot snapshot) {
- Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
- if (!rejectedResponse.isEmpty()) {
- return rejectedResponse;
- }
- ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
- snapshot.setValidationReport(validationReport);
- return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
}
@Override
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
index 47efe6a6772..4b7816cbbbc 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
@@ -117,8 +117,9 @@ public final class ReadwriteSplittingStatusWorkflowPlanningService {
snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
return false;
}
- if (!ensureSupportedIdentifier("database", request.getDatabase(), snapshot) || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.RULE_FIELD, request.getRuleName(), snapshot)
- || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, request.getStorageUnit(), snapshot)) {
+ if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "intaking")
+ || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()), snapshot, "intaking")
+ || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, List.of(request.getStorageUnit()), snapshot, "intaking")) {
snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
return false;
}
@@ -143,16 +144,6 @@ public final class ReadwriteSplittingStatusWorkflowPlanningService {
}
}
- private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
- if (identifier.isEmpty() || WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
- return true;
- }
- snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
- String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
- "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
- return false;
- }
-
private boolean ensureTargetStatusRow(final ReadwriteSplittingStatusWorkflowRequest request, final List<Map<String, Object>> statuses,
final WorkflowContextSnapshot snapshot, final String databaseType) {
if (statuses.stream().anyMatch(each -> matchesStatusTarget(request, each, databaseType))) {
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java
index 70ea8adcbb4..42f7b8a3724 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java
@@ -59,13 +59,7 @@ public final class ReadwriteSplittingStatusWorkflowValidationService implements
public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
final WorkflowContextSnapshot snapshot) {
- Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
- if (!rejectedResponse.isEmpty()) {
- return rejectedResponse;
- }
- ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
- snapshot.setValidationReport(validationReport);
- return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
}
@Override
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
index 3eca34c9806..48dbc6c3810 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
@@ -73,6 +73,8 @@ public final class ShadowWorkflowPlanningService {
private static final List<String> VALIDATION_LAYERS = List.of("rules", "algorithms");
+ private static final String ALGORITHM_ARTIFACT_READY_MESSAGE = "Please use a shadow algorithm visible in the current Proxy and provide required properties.";
+
private final WorkflowPlanningSupport planningSupport = new WorkflowPlanningSupport();
private final ShadowInspectionService inspectionService;
@@ -112,15 +114,11 @@ public final class ShadowWorkflowPlanningService {
ShadowRuleWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.RULE_WORKFLOW_KIND,
resolveIntent(request, "create"), "Shadow rule workflow plan.", RULE_INTERACTION_STEPS);
planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
- if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType()) && !mergedRequest.getDatabase().isEmpty()) {
- planAlgorithms(queryFacade, mergedRequest, result);
- }
+ planAlgorithmsIfRequired(queryFacade, mergedRequest, result);
if (!ensureRulePlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
}
- if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType())
- && !planningSupport.isReadyForArtifactPlanning(mergedRequest, result.getClarifiedIntent(), result, findPropertyRequirements(mergedRequest),
- "Please use a shadow algorithm visible in the current Proxy and provide required properties.")) {
+ if (!isReadyForAlgorithmArtifactPlanning(mergedRequest, result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
}
String databaseType = queryFacade.getDatabaseType(mergedRequest.getDatabase());
@@ -146,15 +144,11 @@ public final class ShadowWorkflowPlanningService {
ShadowDefaultAlgorithmWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.DEFAULT_ALGORITHM_WORKFLOW_KIND,
resolveIntent(request, "create"), "Default shadow algorithm workflow plan.", DEFAULT_ALGORITHM_INTERACTION_STEPS);
planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
- if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType()) && !mergedRequest.getDatabase().isEmpty()) {
- planAlgorithms(queryFacade, mergedRequest, result);
- }
+ planAlgorithmsIfRequired(queryFacade, mergedRequest, result);
if (!ensureDefaultAlgorithmPlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
}
- if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType())
- && !planningSupport.isReadyForArtifactPlanning(mergedRequest, result.getClarifiedIntent(), result, findPropertyRequirements(mergedRequest),
- "Please use a shadow algorithm visible in the current Proxy and provide required properties.")) {
+ if (!isReadyForAlgorithmArtifactPlanning(mergedRequest, result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
}
boolean exists = !inspectionService.queryDefaultAlgorithm(queryFacade, mergedRequest.getDatabase()).isEmpty();
@@ -270,16 +264,26 @@ public final class ShadowWorkflowPlanningService {
}
private boolean ensureSupportedIdentifiers(final WorkflowContextSnapshot snapshot, final String... identifiers) {
- for (String each : identifiers) {
- if (!each.isEmpty() && !WorkflowSQLUtils.isSupportedIdentifier(each)) {
- snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
- String.format("Identifier `%s` contains unsupported characters.", each),
- "Use reviewable logical identifiers without NUL or line terminators.", false, Map.of("identifier", each)));
- snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
- return false;
- }
+ if (planningSupport.ensureOptionalSupportedIdentifiers("", List.of(identifiers), snapshot, "intaking")) {
+ return true;
}
- return true;
+ snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
+ return false;
+ }
+
+ private void planAlgorithmsIfRequired(final MCPFeatureQueryFacade queryFacade, final WorkflowRequest request, final WorkflowContextSnapshot snapshot) {
+ if (!isDropWorkflow(snapshot.getClarifiedIntent()) && !request.getDatabase().isEmpty()) {
+ planAlgorithms(queryFacade, request, snapshot);
+ }
+ }
+
+ private boolean isReadyForAlgorithmArtifactPlanning(final WorkflowRequest request, final WorkflowContextSnapshot snapshot) {
+ return isDropWorkflow(snapshot.getClarifiedIntent())
+ || planningSupport.isReadyForArtifactPlanning(request, snapshot.getClarifiedIntent(), snapshot, findPropertyRequirements(request), ALGORITHM_ARTIFACT_READY_MESSAGE);
+ }
+
+ private boolean isDropWorkflow(final ClarifiedIntent clarifiedIntent) {
+ return WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(clarifiedIntent.getOperationType());
}
private void addMissingInput(final Collection<String> missingInputs, final String value, final String fieldName) {
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java
index 21f392c44da..59da9445489 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java
@@ -67,13 +67,7 @@ public final class ShadowWorkflowValidationService implements MCPWorkflowRuntime
public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
final WorkflowContextSnapshot snapshot) {
- Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
- if (!rejectedResponse.isEmpty()) {
- return rejectedResponse;
- }
- ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
- snapshot.setValidationReport(validationReport);
- return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
}
@Override
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
index 0425f2441d0..80134654573 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
@@ -68,13 +68,7 @@ public final class ShardingWorkflowValidationService implements MCPWorkflowRunti
public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
final WorkflowContextSnapshot snapshot) {
- Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
- if (!rejectedResponse.isEmpty()) {
- return rejectedResponse;
- }
- ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
- snapshot.setValidationReport(validationReport);
- return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
}
@Override
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java
index dfb970321da..d0e6aceac32 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java
@@ -18,9 +18,12 @@
package org.apache.shardingsphere.mcp.support.workflow.service;
import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import java.sql.SQLSyntaxErrorException;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
/**
@@ -41,6 +44,25 @@ public final class WorkflowDistSQLQueryUtils {
return hasSyntaxErrorCause(ex) || hasUnsupportedDistSQLMessage(ex);
}
+ /**
+ * Query DistSQL rule rows, returning an empty list when the current backend does not support the rule DistSQL syntax.
+ *
+ * @param queryFacade query facade
+ * @param databaseName database name
+ * @param sql DistSQL to execute
+ * @return queried rows
+ */
+ public static List<Map<String, Object>> queryRuleRows(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String sql) {
+ try {
+ return queryFacade.query(databaseName, "", sql);
+ } catch (final MCPQueryFailedException ex) {
+ if (isUnsupportedDistSQLQueryFailure(ex)) {
+ return List.of();
+ }
+ throw ex;
+ }
+ }
+
private static boolean hasSyntaxErrorCause(final Throwable throwable) {
Throwable current = throwable;
while (null != current) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
index be9bea56ff5..3e3541bce56 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
@@ -40,6 +40,104 @@ import java.util.Optional;
*/
public final class WorkflowGuidanceResourceHintProvider {
+ private static final Map<String, List<ResourceHintTemplate>> FEATURE_RESOURCE_HINTS = Map.ofEntries(
+ Map.entry(WorkflowKindDescriptors.ENCRYPT_RULE, List.of(new ResourceHintTemplate("shardingsphere://features/encrypt/algorithms", "algorithm", "read_first",
+ "Read encrypt algorithm metadata before choosing algorithm arguments."))),
+ Map.entry(WorkflowKindDescriptors.MASK_RULE, List.of(new ResourceHintTemplate("shardingsphere://features/mask/algorithms", "algorithm", "read_first",
+ "Read mask algorithm metadata before choosing algorithm arguments."))),
+ Map.entry(WorkflowKindDescriptors.READWRITE_RULE,
+ List.of(new ResourceHintTemplate("shardingsphere://features/readwrite-splitting/load-balance-algorithm-plugins", "algorithm", "read_first",
+ "Read load-balance algorithm plugin metadata before choosing algorithm arguments."))),
+ Map.entry(WorkflowKindDescriptors.SHADOW_RULE, List.of(new ResourceHintTemplate("shardingsphere://features/shadow/algorithm-plugins", "algorithm", "read_first",
+ "Read shadow algorithm plugin metadata before choosing algorithm arguments."))),
+ Map.entry(WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM,
+ List.of(new ResourceHintTemplate("shardingsphere://features/shadow/algorithm-plugins", "algorithm", "read_first",
+ "Read shadow algorithm plugin metadata before choosing algorithm arguments."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_TABLE_RULE,
+ List.of(new ResourceHintTemplate("shardingsphere://features/sharding/algorithm-plugins", "algorithm", "read_first",
+ "Read sharding algorithm plugin metadata before choosing algorithm arguments."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY,
+ List.of(new ResourceHintTemplate("shardingsphere://features/sharding/algorithm-plugins", "algorithm", "read_first",
+ "Read sharding algorithm plugin metadata before choosing algorithm arguments."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATOR,
+ List.of(new ResourceHintTemplate("shardingsphere://features/sharding/key-generate-algorithm-plugins", "algorithm", "read_first",
+ "Read key-generate algorithm plugin metadata before choosing generator arguments."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY,
+ List.of(new ResourceHintTemplate("shardingsphere://features/sharding/key-generate-algorithm-plugins", "algorithm", "read_first",
+ "Read key-generate algorithm plugin metadata before choosing generator arguments."))));
+
+ private static final Collection<String> STORAGE_UNIT_WORKFLOW_KINDS = List.of(
+ WorkflowKindDescriptors.READWRITE_RULE, WorkflowKindDescriptors.READWRITE_STATUS, WorkflowKindDescriptors.SHADOW_RULE, WorkflowKindDescriptors.SHARDING_TABLE_RULE);
+
+ private static final Collection<String> SINGLE_TABLE_WORKFLOW_KINDS = List.of(WorkflowKindDescriptors.SHADOW_RULE, WorkflowKindDescriptors.SHARDING_TABLE_RULE);
+
+ private static final Map<String, List<TableResourceHintTemplate>> FEATURE_TABLE_RULE_HINTS = Map.of(
+ WorkflowKindDescriptors.ENCRYPT_RULE, List.of(new TableResourceHintTemplate("shardingsphere://features/encrypt/databases/%s/tables/%s/rules",
+ "Inspect current encrypt table rule DistSQL state before planning changes.")),
+ WorkflowKindDescriptors.MASK_RULE, List.of(new TableResourceHintTemplate("shardingsphere://features/mask/databases/%s/tables/%s/rules",
+ "Inspect current mask table rule DistSQL state before planning changes.")),
+ WorkflowKindDescriptors.SHADOW_RULE, List.of(new TableResourceHintTemplate("shardingsphere://features/shadow/databases/%s/tables/%s/rules",
+ "Inspect current shadow table rule DistSQL state before planning changes.")),
+ WorkflowKindDescriptors.SHARDING_TABLE_RULE, List.of(
+ new TableResourceHintTemplate("shardingsphere://features/sharding/databases/%s/tables/%s/table-rule",
+ "Inspect current sharding table rule DistSQL state before planning changes."),
+ new TableResourceHintTemplate("shardingsphere://features/sharding/databases/%s/tables/%s/nodes",
+ "Inspect current sharding table nodes before planning changes.")));
+
+ private static final Map<String, List<DatabaseResourceHintTemplate>> RULE_RESOURCE_HINTS = Map.ofEntries(
+ Map.entry(WorkflowKindDescriptors.ENCRYPT_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/encrypt/databases/%s/rules",
+ "Inspect current encrypt rules before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.MASK_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/mask/databases/%s/rules",
+ "Inspect current mask rules before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.BROADCAST_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/broadcast/databases/%s/rules",
+ "Inspect current broadcast rules before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.READWRITE_RULE,
+ List.of(new DatabaseResourceHintTemplate("shardingsphere://features/readwrite-splitting/databases/%s/rules",
+ "Inspect current readwrite-splitting rules before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.READWRITE_STATUS,
+ List.of(new DatabaseResourceHintTemplate("shardingsphere://features/readwrite-splitting/databases/%s/status",
+ "Inspect current readwrite-splitting status before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.SHADOW_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/rules",
+ "Inspect current shadow rules before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM,
+ List.of(new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/default-algorithm",
+ "Inspect current default shadow algorithm before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.SHADOW_ALGORITHM_CLEANUP, List.of(
+ new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/algorithms", "Inspect configured shadow algorithms before planning cleanup."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/table-rules", "Inspect shadow table rule references before planning cleanup."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/default-algorithm",
+ "Inspect default shadow algorithm references before planning cleanup."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_TABLE_RULE, List.of(
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/table-rules", "Inspect current sharding table rules before planning changes."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/table-nodes", "Inspect current sharding table nodes before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_TABLE_REFERENCE,
+ List.of(new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/table-reference-rules",
+ "Inspect current sharding table reference rules before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY, List.of(
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/default-strategy",
+ "Inspect current default sharding strategy before planning changes."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/algorithms",
+ "Inspect configured sharding algorithms before planning default strategy changes."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATOR,
+ List.of(new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generators",
+ "Inspect current sharding key generators before planning changes."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY, List.of(
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generate-strategies",
+ "Inspect current sharding key generate strategies before planning changes."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generators",
+ "Inspect current sharding key generators before planning key generate strategy changes."))),
+ Map.entry(WorkflowKindDescriptors.SHARDING_COMPONENT_CLEANUP, List.of(
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/algorithms", "Inspect configured sharding algorithms before planning cleanup."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generators",
+ "Inspect configured sharding key generators before planning cleanup."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/auditors", "Inspect configured sharding auditors before planning cleanup."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/unused-algorithms",
+ "Inspect unused sharding algorithms before planning cleanup."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/unused-key-generators",
+ "Inspect unused sharding key generators before planning cleanup."),
+ new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/unused-auditors",
+ "Inspect unused sharding auditors before planning cleanup."))));
+
/**
* Create resource hints for a workflow planning response.
*
@@ -64,123 +162,33 @@ public final class WorkflowGuidanceResourceHintProvider {
}
private void addFeatureResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot) {
- switch (resolveWorkflowKind(snapshot)) {
- case WorkflowKindDescriptors.ENCRYPT_RULE:
- addResourceHint(resourcesToRead, "shardingsphere://features/encrypt/algorithms", "algorithm", "read_first",
- "Read encrypt algorithm metadata before choosing algorithm arguments.");
- break;
- case WorkflowKindDescriptors.MASK_RULE:
- addResourceHint(resourcesToRead, "shardingsphere://features/mask/algorithms", "algorithm", "read_first",
- "Read mask algorithm metadata before choosing algorithm arguments.");
- break;
- case WorkflowKindDescriptors.READWRITE_RULE:
- addResourceHint(resourcesToRead, "shardingsphere://features/readwrite-splitting/load-balance-algorithm-plugins", "algorithm", "read_first",
- "Read load-balance algorithm plugin metadata before choosing algorithm arguments.");
- break;
- case WorkflowKindDescriptors.SHADOW_RULE:
- case WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM:
- addResourceHint(resourcesToRead, "shardingsphere://features/shadow/algorithm-plugins", "algorithm", "read_first",
- "Read shadow algorithm plugin metadata before choosing algorithm arguments.");
- break;
- case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
- case WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY:
- addResourceHint(resourcesToRead, "shardingsphere://features/sharding/algorithm-plugins", "algorithm", "read_first",
- "Read sharding algorithm plugin metadata before choosing algorithm arguments.");
- break;
- case WorkflowKindDescriptors.SHARDING_KEY_GENERATOR:
- case WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY:
- addResourceHint(resourcesToRead, "shardingsphere://features/sharding/key-generate-algorithm-plugins", "algorithm", "read_first",
- "Read key-generate algorithm plugin metadata before choosing generator arguments.");
- break;
- default:
- break;
+ for (ResourceHintTemplate each : FEATURE_RESOURCE_HINTS.getOrDefault(resolveWorkflowKind(snapshot), List.of())) {
+ addResourceHint(resourcesToRead, each.uri(), each.resourceKind(), each.action(), each.reason());
}
}
private void addGovernanceMetadataResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot, final WorkflowRequest request) {
String workflowKind = resolveWorkflowKind(snapshot);
- switch (workflowKind) {
- case WorkflowKindDescriptors.READWRITE_RULE:
- case WorkflowKindDescriptors.READWRITE_STATUS:
- case WorkflowKindDescriptors.SHADOW_RULE:
- case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
- addStorageUnitsResourceHint(resourcesToRead, request);
- break;
- default:
- break;
+ if (STORAGE_UNIT_WORKFLOW_KINDS.contains(workflowKind)) {
+ addStorageUnitsResourceHint(resourcesToRead, request);
}
- switch (workflowKind) {
- case WorkflowKindDescriptors.SHADOW_RULE:
- case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
- addSingleTablesResourceHint(resourcesToRead, request);
- if (!request.getTable().isEmpty()) {
- addSingleTableResourceHint(resourcesToRead, request);
- }
- break;
- default:
- break;
+ if (SINGLE_TABLE_WORKFLOW_KINDS.contains(workflowKind)) {
+ addSingleTablesResourceHint(resourcesToRead, request);
+ if (!request.getTable().isEmpty()) {
+ addSingleTableResourceHint(resourcesToRead, request);
+ }
}
}
private void addFeatureTableRuleResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot, final WorkflowRequest request) {
- switch (resolveWorkflowKind(snapshot)) {
- case WorkflowKindDescriptors.ENCRYPT_RULE:
- addTableResourceHint(resourcesToRead, request, "shardingsphere://features/encrypt/databases/%s/tables/%s/rules",
- "Inspect current encrypt table rule DistSQL state before planning changes.");
- break;
- case WorkflowKindDescriptors.MASK_RULE:
- addTableResourceHint(resourcesToRead, request, "shardingsphere://features/mask/databases/%s/tables/%s/rules",
- "Inspect current mask table rule DistSQL state before planning changes.");
- break;
- case WorkflowKindDescriptors.SHADOW_RULE:
- addTableResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/tables/%s/rules",
- "Inspect current shadow table rule DistSQL state before planning changes.");
- break;
- case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
- addTableResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/tables/%s/table-rule",
- "Inspect current sharding table rule DistSQL state before planning changes.");
- addTableResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/tables/%s/nodes",
- "Inspect current sharding table nodes before planning changes.");
- break;
- default:
- break;
+ for (TableResourceHintTemplate each : FEATURE_TABLE_RULE_HINTS.getOrDefault(resolveWorkflowKind(snapshot), List.of())) {
+ addTableResourceHint(resourcesToRead, request, each.uriTemplate(), each.reason());
}
}
private void addRuleResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot, final WorkflowRequest request) {
- switch (resolveWorkflowKind(snapshot)) {
- case WorkflowKindDescriptors.ENCRYPT_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/encrypt/databases/%s/rules",
- "Inspect current encrypt rules before planning changes.");
- case WorkflowKindDescriptors.MASK_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/mask/databases/%s/rules",
- "Inspect current mask rules before planning changes.");
- case WorkflowKindDescriptors.BROADCAST_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/broadcast/databases/%s/rules",
- "Inspect current broadcast rules before planning changes.");
- case WorkflowKindDescriptors.READWRITE_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/readwrite-splitting/databases/%s/rules",
- "Inspect current readwrite-splitting rules before planning changes.");
- case WorkflowKindDescriptors.READWRITE_STATUS -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/readwrite-splitting/databases/%s/status",
- "Inspect current readwrite-splitting status before planning changes.");
- case WorkflowKindDescriptors.SHADOW_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/rules",
- "Inspect current shadow rules before planning changes.");
- case WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/default-algorithm",
- "Inspect current default shadow algorithm before planning changes.");
- case WorkflowKindDescriptors.SHADOW_ALGORITHM_CLEANUP -> {
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/algorithms",
- "Inspect configured shadow algorithms before planning cleanup.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/table-rules",
- "Inspect shadow table rule references before planning cleanup.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/default-algorithm",
- "Inspect default shadow algorithm references before planning cleanup.");
- }
- case WorkflowKindDescriptors.SHARDING_TABLE_RULE -> addShardingTableRuleResources(resourcesToRead, request);
- case WorkflowKindDescriptors.SHARDING_TABLE_REFERENCE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/table-reference-rules",
- "Inspect current sharding table reference rules before planning changes.");
- case WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY -> addShardingDefaultStrategyResources(resourcesToRead, request);
- case WorkflowKindDescriptors.SHARDING_KEY_GENERATOR -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generators",
- "Inspect current sharding key generators before planning changes.");
- case WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY -> addShardingKeyGenerateStrategyResources(resourcesToRead, request);
- case WorkflowKindDescriptors.SHARDING_COMPONENT_CLEANUP -> addShardingComponentCleanupResources(resourcesToRead, request);
- default -> {
- }
+ for (DatabaseResourceHintTemplate each : RULE_RESOURCE_HINTS.getOrDefault(resolveWorkflowKind(snapshot), List.of())) {
+ addDatabaseResourceHint(resourcesToRead, request, each.uriTemplate(), each.reason());
}
}
@@ -260,42 +268,6 @@ public final class WorkflowGuidanceResourceHintProvider {
resourcesToRead.add(MCPResourceHintUtils.create(uri, resourceKind, action, reason, MCPPayloadFieldNames.RESOURCES_TO_READ));
}
- private void addShardingTableRuleResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/table-rules",
- "Inspect current sharding table rules before planning changes.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/table-nodes",
- "Inspect current sharding table nodes before planning changes.");
- }
-
- private void addShardingDefaultStrategyResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/default-strategy",
- "Inspect current default sharding strategy before planning changes.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/algorithms",
- "Inspect configured sharding algorithms before planning default strategy changes.");
- }
-
- private void addShardingKeyGenerateStrategyResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generate-strategies",
- "Inspect current sharding key generate strategies before planning changes.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generators",
- "Inspect current sharding key generators before planning key generate strategy changes.");
- }
-
- private void addShardingComponentCleanupResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/algorithms",
- "Inspect configured sharding algorithms before planning cleanup.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generators",
- "Inspect configured sharding key generators before planning cleanup.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/auditors",
- "Inspect configured sharding auditors before planning cleanup.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/unused-algorithms",
- "Inspect unused sharding algorithms before planning cleanup.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/unused-key-generators",
- "Inspect unused sharding key generators before planning cleanup.");
- addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/unused-auditors",
- "Inspect unused sharding auditors before planning cleanup.");
- }
-
private void addTableResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
resourcesToRead.add(MCPResourceHintUtils.create(String.format("shardingsphere://databases/%s/schemas/%s/tables/%s/columns", MCPUriPathSegmentUtils.encodePathSegment(request.getDatabase()),
MCPUriPathSegmentUtils.encodePathSegment(request.getSchema()), MCPUriPathSegmentUtils.encodePathSegment(request.getTable())),
@@ -306,4 +278,13 @@ public final class WorkflowGuidanceResourceHintProvider {
WorkflowKind workflowKind = snapshot.getWorkflowKind();
return null == workflowKind ? "" : workflowKind.getValue();
}
+
+ private record ResourceHintTemplate(String uri, String resourceKind, String action, String reason) {
+ }
+
+ private record DatabaseResourceHintTemplate(String uriTemplate, String reason) {
+ }
+
+ private record TableResourceHintTemplate(String uriTemplate, String reason) {
+ }
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
index fa80cbd2f8a..e29ea134928 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
@@ -24,6 +24,8 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
+import java.util.Collection;
+import java.util.List;
import java.util.Locale;
/**
@@ -34,6 +36,17 @@ public final class WorkflowIntentResolverSupport {
private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
+ private static final List<FieldSemanticRule> FIELD_SEMANTIC_RULES = List.of(
+ new FieldSemanticRule("phone", List.of("phone number", "phone", "mobile", "tel", "手机号", "手机", "电话号码", "电话"), List.of("phone", "mobile", "tel")),
+ new FieldSemanticRule("id_card", List.of("identity card", "id card", "身份证", "证件"), List.of("id_card")),
+ new FieldSemanticRule("email", List.of("email", "邮箱", "邮件"), List.of("email")),
+ new FieldSemanticRule("bank_card", List.of("bank card", "银行卡", "银行卡号", "卡号"), List.of("bank_card", "card_no", "card_number")),
+ new FieldSemanticRule("passport", List.of("passport", "护照"), List.of("passport")),
+ new FieldSemanticRule("license_plate", List.of("license plate", "车牌", "车牌号"), List.of("license_plate", "plate_no", "plate_number")),
+ new FieldSemanticRule("birth_date", List.of("birth date", "birthday", "出生日期", "生日"), List.of("birth_date", "birthday", "date_of_birth")),
+ new FieldSemanticRule("address", List.of("address", "地址", "住址"), List.of("address", "addr")),
+ new FieldSemanticRule("name", List.of("real name", "full name", "姓名", "名字"), List.of("real_name", "full_name", "name")));
+
/**
* Resolve workflow operation type from explicit fields and heuristics.
*
@@ -90,37 +103,10 @@ public final class WorkflowIntentResolverSupport {
}
String naturalLanguageIntent = getNaturalLanguageIntent(request);
String columnName = request.getColumn().toLowerCase(Locale.ENGLISH);
- if (containsAny(naturalLanguageIntent, "phone number", "phone", "mobile", "tel", "手机号", "手机", "电话号码", "电话")
- || containsAny(columnName, "phone", "mobile", "tel")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "phone");
- }
- if (containsAny(naturalLanguageIntent, "identity card", "id card", "身份证", "证件") || columnName.contains("id_card")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "id_card");
- }
- if (containsAny(naturalLanguageIntent, "email", "邮箱", "邮件") || columnName.contains("email")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "email");
- }
- if (containsAny(naturalLanguageIntent, "bank card", "银行卡", "银行卡号", "卡号")
- || containsAny(columnName, "bank_card", "card_no", "card_number")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "bank_card");
- }
- if (containsAny(naturalLanguageIntent, "passport", "护照") || columnName.contains("passport")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "passport");
- }
- if (containsAny(naturalLanguageIntent, "license plate", "车牌", "车牌号")
- || containsAny(columnName, "license_plate", "plate_no", "plate_number")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "license_plate");
- }
- if (containsAny(naturalLanguageIntent, "birth date", "birthday", "出生日期", "生日")
- || containsAny(columnName, "birth_date", "birthday", "date_of_birth")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "birth_date");
- }
- if (containsAny(naturalLanguageIntent, "address", "地址", "住址") || containsAny(columnName, "address", "addr")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "address");
- }
- if (containsAny(naturalLanguageIntent, "real name", "full name", "姓名", "名字")
- || containsAny(columnName, "real_name", "full_name", "name")) {
- return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "name");
+ for (FieldSemanticRule each : FIELD_SEMANTIC_RULES) {
+ if (each.matches(naturalLanguageIntent, columnName)) {
+ return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, each.semantics());
+ }
}
return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, columnName);
}
@@ -170,10 +156,26 @@ public final class WorkflowIntentResolverSupport {
return false;
}
+ private static boolean containsAny(final String value, final Collection<String> candidates) {
+ for (String each : candidates) {
+ if (value.contains(each)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static String recordInferredValue(final ClarifiedIntent clarifiedIntent, final String fieldName, final String value) {
if (null != clarifiedIntent) {
clarifiedIntent.getInferredValues().put(fieldName, value);
}
return value;
}
+
+ private record FieldSemanticRule(String semantics, Collection<String> intentKeywords, Collection<String> columnKeywords) {
+
+ private boolean matches(final String naturalLanguageIntent, final String columnName) {
+ return containsAny(naturalLanguageIntent, intentKeywords) || containsAny(columnName, columnKeywords);
+ }
+ }
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
index 6118cc8abb1..d6d2af27db4 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
@@ -32,6 +32,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowKind;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -170,6 +171,43 @@ public final class WorkflowPlanningSupport {
return collectPropertyRequirements(request, clarifiedIntent, snapshot, propertyRequirements);
}
+ /**
+ * Ensure workflow identifiers can be rendered into reviewable DistSQL.
+ *
+ * @param fieldName field name for issue details
+ * @param identifiers identifiers to check
+ * @param snapshot workflow snapshot
+ * @param issueStage issue stage
+ * @return whether all identifiers are supported
+ */
+ public boolean ensureSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot,
+ final String issueStage) {
+ return ensureIdentifiers(fieldName, identifiers, snapshot, issueStage, false);
+ }
+
+ /**
+ * Ensure optional workflow identifiers can be rendered into reviewable DistSQL when present.
+ *
+ * @param fieldName field name for issue details
+ * @param identifiers identifiers to check
+ * @param snapshot workflow snapshot
+ * @param issueStage issue stage
+ * @return whether all present identifiers are supported
+ */
+ public boolean ensureOptionalSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot,
+ final String issueStage) {
+ return ensureIdentifiers(fieldName, identifiers, snapshot, issueStage, true);
+ }
+
+ private boolean ensureIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot, final String issueStage, final boolean allowEmpty) {
+ for (String each : identifiers) {
+ if (!ensureSupportedIdentifier(fieldName, each, snapshot, issueStage, allowEmpty)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Ensure workflow planning context is complete and valid.
*
@@ -188,16 +226,16 @@ public final class WorkflowPlanningSupport {
snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
return false;
}
- if (!ensureSupportedIdentifier(WorkflowFieldNames.DATABASE, request.getDatabase(), snapshot)
- || !ensureSupportedIdentifier(WorkflowFieldNames.TABLE, request.getTable(), snapshot)
- || !ensureSupportedIdentifier(WorkflowFieldNames.COLUMN, request.getColumn(), snapshot)) {
+ if (!ensureSupportedIdentifiers(WorkflowFieldNames.DATABASE, List.of(request.getDatabase()), snapshot, "discovering")
+ || !ensureSupportedIdentifiers(WorkflowFieldNames.TABLE, List.of(request.getTable()), snapshot, "discovering")
+ || !ensureSupportedIdentifiers(WorkflowFieldNames.COLUMN, List.of(request.getColumn()), snapshot, "discovering")) {
snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
return false;
}
Optional<MCPDatabaseMetadata> databaseMetadata = metadataQueryFacade.queryDatabase(WorkflowSQLUtils.normalizeIdentifier(request.getDatabase()));
String databaseType = databaseMetadata.map(MCPDatabaseMetadata::getDatabaseType).orElse("");
request.setSchema(resolveSchema(databaseMetadata, request, clarifiedIntent, databaseType));
- if (!ensureSupportedIdentifier(WorkflowFieldNames.SCHEMA, request.getSchema(), snapshot)) {
+ if (!ensureSupportedIdentifiers(WorkflowFieldNames.SCHEMA, List.of(request.getSchema()), snapshot, "discovering")) {
snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
return false;
}
@@ -241,16 +279,31 @@ public final class WorkflowPlanningSupport {
}
}
- private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
- if (WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
+ private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot, final String issueStage, final boolean allowEmpty) {
+ if (allowEmpty && identifier.isEmpty() || WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
return true;
}
- snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "discovering",
- String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
- "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
+ snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", issueStage, createUnsupportedIdentifierMessage(fieldName, identifier),
+ createUnsupportedIdentifierAction(fieldName), false, createUnsupportedIdentifierDetails(fieldName, identifier)));
return false;
}
+ private String createUnsupportedIdentifierMessage(final String fieldName, final String identifier) {
+ return fieldName.isEmpty()
+ ? String.format("Identifier `%s` contains unsupported characters.", identifier)
+ : String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier);
+ }
+
+ private String createUnsupportedIdentifierAction(final String fieldName) {
+ return fieldName.isEmpty()
+ ? "Use reviewable logical identifiers without NUL or line terminators."
+ : "Use a reviewable logical identifier without NUL or line terminators.";
+ }
+
+ private Map<String, Object> createUnsupportedIdentifierDetails(final String fieldName, final String identifier) {
+ return fieldName.isEmpty() ? Map.of("identifier", identifier) : Map.of("field", fieldName, "identifier", identifier);
+ }
+
private String resolveSchema(final Optional<MCPDatabaseMetadata> databaseMetadata, final WorkflowRequest request, final ClarifiedIntent clarifiedIntent, final String databaseType) {
String actualSchema = request.getSchema();
if (!isEmptyIdentifier(actualSchema)) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
index d43b54bffdf..1b46bed5434 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
@@ -32,6 +32,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Supplier;
/**
* Workflow validation support.
@@ -58,6 +59,26 @@ public final class WorkflowValidationSupport {
return Map.of();
}
+ /**
+ * Validate workflow snapshot and persist validation response.
+ *
+ * @param workflowSessionContext workflow session context
+ * @param sessionId session identifier
+ * @param snapshot workflow snapshot
+ * @param validationReportSupplier validation report supplier
+ * @return validation response
+ */
+ public Map<String, Object> validateAndFinalize(final WorkflowSessionContext workflowSessionContext, final String sessionId, final WorkflowContextSnapshot snapshot,
+ final Supplier<ValidationReport> validationReportSupplier) {
+ Map<String, Object> rejectedResponse = checkValidatePreconditions(sessionId, snapshot);
+ if (!rejectedResponse.isEmpty()) {
+ return rejectedResponse;
+ }
+ ValidationReport validationReport = validationReportSupplier.get();
+ snapshot.setValidationReport(validationReport);
+ return finalizeValidation(workflowSessionContext, snapshot, validationReport);
+ }
+
/**
* Resolve overall validation status from validation sections.
*
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java
index ec2809e8f6d..db3b44c10ab 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java
@@ -18,13 +18,22 @@
package org.apache.shardingsphere.mcp.support.workflow.service;
import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
+import java.util.List;
+import java.util.Map;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
class WorkflowDistSQLQueryUtilsTest {
@@ -46,4 +55,28 @@ class WorkflowDistSQLQueryUtilsTest {
MCPQueryFailedException actualException = new MCPQueryFailedException("Connection refused.", new SQLException("Connection refused."));
assertFalse(WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(actualException));
}
+
+ @Test
+ void assertQueryRuleRows() {
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ List<Map<String, Object>> expected = List.of(Map.of("name", "orders"));
+ when(queryFacade.query("logic_db", "", "SHOW MASK RULES")).thenReturn(expected);
+ assertThat(WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, "logic_db", "SHOW MASK RULES"), is(expected));
+ }
+
+ @Test
+ void assertQueryRuleRowsWithUnsupportedDistSQL() {
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ when(queryFacade.query("logic_db", "", "SHOW MASK RULES")).thenThrow(new MCPQueryFailedException("DistSQL syntax is unsupported by this runtime backend."));
+ assertTrue(WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, "logic_db", "SHOW MASK RULES").isEmpty());
+ }
+
+ @Test
+ void assertQueryRuleRowsWithConnectionFailure() {
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ MCPQueryFailedException expected = new MCPQueryFailedException("Connection refused.", new SQLException("Connection refused."));
+ when(queryFacade.query("logic_db", "", "SHOW MASK RULES")).thenThrow(expected);
+ MCPQueryFailedException actual = assertThrows(MCPQueryFailedException.class, () -> WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, "logic_db", "SHOW MASK RULES"));
+ assertThat(actual, sameInstance(expected));
+ }
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
index bee145b6509..d971ea1a162 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
@@ -228,6 +228,24 @@ class WorkflowPlanningSupportTest {
assertThat(clarifiedIntent.getInferredValues().get("execution_mode"), is("manual-only"));
}
+ @Test
+ void assertEnsureOptionalSupportedIdentifiersAllowsEmptyIdentifier() {
+ WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+ boolean actual = planningSupport.ensureOptionalSupportedIdentifiers("rule", List.of(""), snapshot, "intaking");
+ assertTrue(actual);
+ assertTrue(snapshot.getIssues().isEmpty());
+ }
+
+ @Test
+ void assertEnsureSupportedIdentifiersRejectsUnsupportedIdentifier() {
+ WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+ boolean actual = planningSupport.ensureSupportedIdentifiers("", List.of("orders\ndrop"), snapshot, "intaking");
+ assertFalse(actual);
+ assertThat(snapshot.getIssues().getFirst().getMessage(), is("Identifier `orders\ndrop` contains unsupported characters."));
+ assertThat(snapshot.getIssues().getFirst().getUserAction(), is("Use reviewable logical identifiers without NUL or line terminators."));
+ assertThat(snapshot.getIssues().getFirst().getDetails(), is(Map.of("identifier", "orders\ndrop")));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("getEnsureLifecycleStateCases")
void assertEnsureLifecycleState(final String name, final String operationType, final boolean ruleExists, final boolean expectedResult, final String expectedIssueCode) {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java
index 559e4f8e792..2aeee3790bc 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java
@@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -77,6 +78,37 @@ class WorkflowValidationSupportTest {
assertTrue(validationSupport.checkValidatePreconditions("session-1", snapshot).isEmpty());
}
+ @Test
+ void assertValidateAndFinalize() {
+ WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+ snapshot.setPlanId("plan-1");
+ snapshot.setSessionId("session-1");
+ snapshot.setStatus("executed");
+ ValidationReport validationReport = new ValidationReport();
+ validationReport.setOverallStatus("passed");
+ WorkflowSessionContext workflowSessionContext = new TestWorkflowSessionContext();
+ workflowSessionContext.save(snapshot);
+ Map<String, Object> actualResult = validationSupport.validateAndFinalize(workflowSessionContext, "session-1", snapshot, () -> validationReport);
+ assertThat(actualResult.get("response_mode"), is("validation"));
+ assertThat(actualResult.get("status"), is("validated"));
+ assertThat(snapshot.getValidationReport(), is(validationReport));
+ }
+
+ @Test
+ void assertValidateAndFinalizeRejectsInvalidPrecondition() {
+ WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+ snapshot.setPlanId("plan-1");
+ snapshot.setSessionId("session-1");
+ snapshot.setStatus("clarifying");
+ AtomicBoolean reportCreated = new AtomicBoolean();
+ Map<String, Object> actualResult = validationSupport.validateAndFinalize(new TestWorkflowSessionContext(), "session-1", snapshot, () -> {
+ reportCreated.set(true);
+ return new ValidationReport();
+ });
+ assertThat(actualResult.get("response_mode"), is("terminal"));
+ assertFalse(reportCreated.get());
+ }
+
@Test
void assertResolveOverallStatusReturnsFailedWhenAnySectionFails() {
String actualStatus = validationSupport.resolveOverallStatus(
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
index b7fefc7c313..5bc0b1caff0 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
@@ -142,6 +142,21 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
return result;
}
+ protected final String createMaskRulePlan(final HttpClient httpClient, final String sessionId) throws IOException, InterruptedException {
+ HttpResponse<String> actual = sendToolCallRequest(httpClient, sessionId, "database_gateway_plan_mask_rule", Map.of(
+ "database", "logic_db",
+ "schema", "logic_db",
+ "table", "orders",
+ "column", "status",
+ "operation_type", "create",
+ "algorithm_type", "KEEP_FIRST_N_LAST_M",
+ "primary_algorithm_properties", Map.of("first-n", "1", "last-m", "1", "replace-char", "*")));
+ assertThat(actual.statusCode(), is(200));
+ Map<String, Object> payload = getStructuredContent(actual.body());
+ assertThat(String.valueOf(payload.get("status")), is("planned"));
+ return String.valueOf(payload.get("plan_id"));
+ }
+
protected final URI getEndpointUri() throws IOException {
return getHttpEndpointUri();
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
index fd212ed5e8d..909444bd32f 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
@@ -110,21 +110,6 @@ class HttpTransportApprovalSafetyE2ETest extends AbstractSharedHttpProgrammaticR
return getStructuredContent(actual.body());
}
- private String createMaskRulePlan(final HttpClient httpClient, final String sessionId) throws IOException, InterruptedException {
- HttpResponse<String> actual = sendToolCallRequest(httpClient, sessionId, "database_gateway_plan_mask_rule", Map.of(
- "database", "logic_db",
- "schema", "logic_db",
- "table", "orders",
- "column", "status",
- "operation_type", "create",
- "algorithm_type", "KEEP_FIRST_N_LAST_M",
- "primary_algorithm_properties", Map.of("first-n", "1", "last-m", "1", "replace-char", "*")));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- assertThat(String.valueOf(payload.get("status")), is("planned"));
- return String.valueOf(payload.get("plan_id"));
- }
-
private void assertModelFacingPayloadContract(final Map<String, Object> payload) {
MCPModelContractAssertions.assertCanonicalNextActionLists(payload);
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
index ef1152a7ed1..b182584f9af 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
@@ -154,18 +154,4 @@ class HttpTransportCompletionE2ETest extends AbstractSharedHttpProgrammaticRunti
"completion-1", "completion/complete", params));
}
- private String createMaskRulePlan(final HttpClient httpClient, final String sessionId) throws IOException, InterruptedException {
- HttpResponse<String> actual = sendToolCallRequest(httpClient, sessionId, "database_gateway_plan_mask_rule", Map.of(
- "database", "logic_db",
- "schema", "logic_db",
- "table", "orders",
- "column", "status",
- "operation_type", "create",
- "algorithm_type", "KEEP_FIRST_N_LAST_M",
- "primary_algorithm_properties", Map.of("first-n", "1", "last-m", "1", "replace-char", "*")));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- assertThat(String.valueOf(payload.get("status")), is("planned"));
- return String.valueOf(payload.get("plan_id"));
- }
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
index 68c6540c7fd..1ad17219004 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
@@ -203,14 +203,7 @@ public final class MySQLRuntimeTestSupport {
* @throws SQLException SQL exception
*/
public static void initializeDatabase(final GenericContainer<?> container) throws SQLException {
- executeStatements(container, DATABASE_NAME,
- "CREATE TABLE IF NOT EXISTS orders (order_id INT PRIMARY KEY, status VARCHAR(32), amount INT)",
- "CREATE TABLE IF NOT EXISTS order_items (item_id INT PRIMARY KEY, order_id INT, sku VARCHAR(64))",
- "INSERT INTO orders (order_id, status, amount) VALUES (1, 'NEW', 10) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
- "INSERT INTO orders (order_id, status, amount) VALUES (2, 'DONE', 20) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
- "INSERT INTO order_items (item_id, order_id, sku) VALUES (1, 1, 'sku-1') ON DUPLICATE KEY UPDATE order_id = VALUES(order_id), sku = VALUES(sku)",
- "CREATE OR REPLACE VIEW active_orders AS SELECT order_id, status FROM orders WHERE status <> 'DONE'",
- "CREATE INDEX idx_orders_status ON orders(status)");
+ initializeOrdersSchema(container, DATABASE_NAME);
}
private static void initializeProgrammaticDatabases(final GenericContainer<?> container) throws SQLException {
@@ -228,14 +221,7 @@ public final class MySQLRuntimeTestSupport {
"GRANT ALL PRIVILEGES ON analytics_db.* TO 'mcp_analytics'@'%'",
"GRANT ALL PRIVILEGES ON warehouse.* TO 'mcp_warehouse'@'%'",
"FLUSH PRIVILEGES");
- executeStatements(container, "logic_db",
- "CREATE TABLE IF NOT EXISTS orders (order_id INT PRIMARY KEY, status VARCHAR(32), amount INT)",
- "CREATE TABLE IF NOT EXISTS order_items (item_id INT PRIMARY KEY, order_id INT, sku VARCHAR(64))",
- "INSERT INTO orders (order_id, status, amount) VALUES (1, 'NEW', 10) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
- "INSERT INTO orders (order_id, status, amount) VALUES (2, 'DONE', 20) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
- "INSERT INTO order_items (item_id, order_id, sku) VALUES (1, 1, 'sku-1') ON DUPLICATE KEY UPDATE order_id = VALUES(order_id), sku = VALUES(sku)",
- "CREATE OR REPLACE VIEW active_orders AS SELECT order_id, status FROM orders WHERE status <> 'DONE'",
- "CREATE INDEX idx_orders_status ON orders(status)");
+ initializeOrdersSchema(container, "logic_db");
executeStatements(container, "analytics_db",
"CREATE TABLE IF NOT EXISTS metrics (metric_id INT PRIMARY KEY, metric_name VARCHAR(32))",
"INSERT INTO metrics (metric_id, metric_name) VALUES (10, 'cpu') ON DUPLICATE KEY UPDATE metric_name = VALUES(metric_name)",
@@ -246,6 +232,17 @@ public final class MySQLRuntimeTestSupport {
"INSERT INTO facts (fact_id, total) VALUES (200, 2) ON DUPLICATE KEY UPDATE total = VALUES(total)");
}
+ private static void initializeOrdersSchema(final GenericContainer<?> container, final String databaseName) throws SQLException {
+ executeStatements(container, databaseName,
+ "CREATE TABLE IF NOT EXISTS orders (order_id INT PRIMARY KEY, status VARCHAR(32), amount INT)",
+ "CREATE TABLE IF NOT EXISTS order_items (item_id INT PRIMARY KEY, order_id INT, sku VARCHAR(64))",
+ "INSERT INTO orders (order_id, status, amount) VALUES (1, 'NEW', 10) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
+ "INSERT INTO orders (order_id, status, amount) VALUES (2, 'DONE', 20) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
+ "INSERT INTO order_items (item_id, order_id, sku) VALUES (1, 1, 'sku-1') ON DUPLICATE KEY UPDATE order_id = VALUES(order_id), sku = VALUES(sku)",
+ "CREATE OR REPLACE VIEW active_orders AS SELECT order_id, status FROM orders WHERE status <> 'DONE'",
+ "CREATE INDEX idx_orders_status ON orders(status)");
+ }
+
/**
* Detect the schema value surfaced by JDBC metadata for the orders table.
*