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 <[email protected]>
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.
*