You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/07/11 22:24:36 UTC
(shardingsphere) branch master updated: Remove unknown database capability fallbacks from MCP (#39073)
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 398e63e26b6 Remove unknown database capability fallbacks from MCP (#39073)
398e63e26b6 is described below
commit 398e63e26b6878375cc0c582dbb101c33eeaf31d
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Sun Jul 12 06:24:28 2026 +0800
Remove unknown database capability fallbacks from MCP (#39073)
* Remove unknown database capability fallbacks from MCP
* Remove unknown database capability fallbacks from MCP
---
.../core/workflow/WorkflowProxyQueryService.java | 4 +-
.../performance/MCPPerformanceBudgetSmokeTest.java | 4 +-
.../handler/CoreResourceHandlerSurfaceTest.java | 3 +-
.../tool/handler/ToolDefinitionRegistryTest.java | 4 +-
.../metadata/SearchMetadataToolHandlerTest.java | 59 +++++++++------
.../workflow/WorkflowProxyQueryServiceTest.java | 54 ++++---------
.../handler/BroadcastTableRuleHandlerTest.java | 24 ++++--
.../BroadcastWorkflowPlanningServiceTest.java | 21 ++++++
.../BroadcastWorkflowValidationServiceTest.java | 23 ++++--
.../service/EncryptWorkflowPlanningService.java | 5 +-
.../EncryptWorkflowPlanningServiceTest.java | 64 ++++++++++++----
.../EncryptWorkflowValidationServiceTest.java | 76 +++++++++++++------
.../tool/service/MaskWorkflowPlanningService.java | 2 +-
.../service/MaskWorkflowPlanningServiceTest.java | 62 +++++++++++----
.../service/MaskWorkflowValidationServiceTest.java | 70 ++++++++++++-----
...dwriteSplittingWorkflowPlanningServiceTest.java | 21 ++++++
...riteSplittingWorkflowValidationServiceTest.java | 38 ++++++++--
.../service/ShadowWorkflowPlanningServiceTest.java | 24 ++++--
.../ShadowWorkflowValidationServiceTest.java | 24 ++++--
.../ShardingWorkflowValidationServiceTest.java | 10 ++-
.../database/capability/MCPDatabaseDialect.java | 64 +++++-----------
.../database/spi/MCPFeatureQueryFacade.java | 4 +-
.../service/WorkflowPlanningContextValidator.java | 13 ++--
.../workflow/service/WorkflowPlanningSupport.java | 8 +-
.../MCPDatabaseCapabilityProviderTest.java | 8 ++
.../capability/MCPDatabaseDialectTest.java | 88 +++++++++++++---------
.../WorkflowPlanningContextValidatorTest.java | 85 +++++++++++++++++++--
.../service/WorkflowPlanningSupportTest.java | 39 ++++++----
.../workflow/service/WorkflowSQLUtilsTest.java | 20 +++--
29 files changed, 626 insertions(+), 295 deletions(-)
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java
index 192680e7cab..cde3be0cdcf 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryService.java
@@ -23,6 +23,7 @@ import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnavailableExcept
import org.apache.shardingsphere.mcp.core.session.MCPSessionManager;
import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapabilityProvider;
+import org.apache.shardingsphere.mcp.support.database.exception.DatabaseCapabilityNotFoundException;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
@@ -72,7 +73,8 @@ public final class WorkflowProxyQueryService implements MCPFeatureQueryFacade {
@Override
public String getDatabaseType(final String databaseName) {
- return databaseCapabilityProvider.provide(WorkflowSQLUtils.normalizeIdentifier(databaseName)).map(MCPDatabaseCapability::getDatabaseType).orElse("");
+ return databaseCapabilityProvider.provide(WorkflowSQLUtils.normalizeIdentifier(databaseName)).map(MCPDatabaseCapability::getDatabaseType)
+ .orElseThrow(DatabaseCapabilityNotFoundException::new);
}
@Override
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java
index fcd9f704d4f..cb5f7661bc6 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java
@@ -23,6 +23,7 @@ import org.apache.shardingsphere.mcp.core.completion.provider.WorkflowPlanIdComp
import org.apache.shardingsphere.mcp.core.context.MCPRequestScope;
import org.apache.shardingsphere.mcp.core.context.MCPRuntimeContext;
import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory;
+import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.RequestScopeFixture;
import org.apache.shardingsphere.mcp.core.resource.handler.capability.ServerCapabilitiesHandler;
import org.apache.shardingsphere.mcp.core.tool.handler.execute.StatementClassifier;
import org.apache.shardingsphere.mcp.core.tool.handler.metadata.SearchMetadataToolHandler;
@@ -108,7 +109,8 @@ class MCPPerformanceBudgetSmokeTest {
@Test
void assertMetadataSearchBudget() {
MCPRuntimeContext runtimeContext = ResourceTestDataFactory.createRuntimeContext();
- try (MCPRequestScope requestScope = new MCPRequestScope(runtimeContext)) {
+ try (RequestScopeFixture requestScopeFixture = ResourceTestDataFactory.createRequestScopeFixture(runtimeContext, ResourceTestDataFactory.createDatabaseMetadata())) {
+ MCPRequestScope requestScope = requestScopeFixture.getRequestScope();
SearchMetadataToolHandler handler = new SearchMetadataToolHandler();
Map<String, Object> arguments = Map.of("query", "order", "object_types", List.of("table"));
assertDoesNotThrow(() -> handler.handle(requestScope, new MCPToolCall("session-1", arguments)));
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/CoreResourceHandlerSurfaceTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/CoreResourceHandlerSurfaceTest.java
index 50bba8a99f7..f41229b8307 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/CoreResourceHandlerSurfaceTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/CoreResourceHandlerSurfaceTest.java
@@ -102,7 +102,8 @@ class CoreResourceHandlerSurfaceTest {
@Test
void assertHandleWithoutIndexMetadata() {
- try (MCPRequestScope requestContext = new MCPRequestScope(runtimeContext)) {
+ try (RequestScopeFixture requestScopeFixture = ResourceTestDataFactory.createRequestScopeFixture(runtimeContext, ResourceTestDataFactory.createDatabaseMetadata())) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new MetadataResourceHandler(
"shardingsphere://databases/{database}/schemas/{schema}/tables/{table}/indexes",
(featureContext, uriVariables) -> featureContext.getMetadataQueryFacade().queryIndexes(
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java
index 8bd025cd87d..904f0fa9bcd 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/ToolDefinitionRegistryTest.java
@@ -33,6 +33,7 @@ import org.apache.shardingsphere.mcp.core.protocol.exception.MCPInvalidToolArgum
import org.apache.shardingsphere.mcp.core.protocol.exception.MCPToolArgumentContractViolationException;
import org.apache.shardingsphere.mcp.core.protocol.exception.UnsupportedToolException;
import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory;
+import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.RequestScopeFixture;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
@@ -93,7 +94,8 @@ class ToolDefinitionRegistryTest {
void assertDispatch() {
MCPRuntimeContext runtimeContext = ResourceTestDataFactory.createRuntimeContext();
runtimeContext.getSessionManager().createSession("session-1");
- try (MCPRequestScope requestContext = new MCPRequestScope(runtimeContext)) {
+ try (RequestScopeFixture requestScopeFixture = ResourceTestDataFactory.createRequestScopeFixture(runtimeContext, ResourceTestDataFactory.createDatabaseMetadata())) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPToolDefinition toolDefinition = ToolDefinitionRegistry.getToolDefinition("database_gateway_search_metadata");
MCPResponse actual = ToolDefinitionRegistry.dispatch(requestContext, toolDefinition, "session-1", Map.of("query", "order", "object_types", List.of("index")));
assertThat(toolDefinition.getDescriptor().getName(), is("database_gateway_search_metadata"));
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolHandlerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolHandlerTest.java
index 7f61d4b3fb1..3ae36053cf2 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolHandlerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/metadata/SearchMetadataToolHandlerTest.java
@@ -26,6 +26,7 @@ import org.apache.shardingsphere.mcp.core.context.MCPRuntimeContext;
import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory;
import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.DatabaseMetadataFixture;
import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.RequestScopeFixture;
+import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.TableMetadataFixture;
import org.apache.shardingsphere.mcp.core.tool.response.MetadataSearchHit;
import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPMetadataObjectType;
@@ -38,6 +39,8 @@ import org.junit.jupiter.api.Test;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -81,7 +84,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadata() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual =
new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1",
Map.of("query", "order", "object_types", List.of(SupportedMCPMetadataObjectType.INDEX.name()))));
@@ -133,7 +137,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataRejectsQueryInObjectTypes() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1",
Map.of("database", "logic_db", "schema", "public", "query", "orders", "object_types", List.of("table", "orders")))));
assertThat(actual.getMessage(), is("Unsupported object_types value `orders`."));
@@ -142,7 +147,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithEmptyQuery() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of("database", "logic_db")));
Map<String, Object> actualPayload = actual.toPayload();
List<String> actualNames = new LinkedList<>();
@@ -161,7 +167,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithLargeResultGuidance() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext(createLargeDatabaseMetadata()))) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope(createLargeDatabaseMetadata())) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1",
Map.of("database", "large_db", "object_types", List.of(SupportedMCPMetadataObjectType.TABLE.name()))));
Map<String, Object> actualPayload = actual.toPayload();
@@ -186,7 +193,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithBlankAllDatabaseGuard() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of()));
Map<String, Object> actualPayload = actual.toPayload();
List<String> actualNames = new LinkedList<>();
@@ -206,7 +214,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithEmptyResultGuidance() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of("database", "logic_db", "query", "missing")));
Map<String, Object> actualPayload = actual.toPayload();
assertThat(actualPayload.get("summary"), is("Metadata search returned 0 of 0 matches."));
@@ -222,7 +231,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithObjectNotVisibleInKnownSchema() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1",
Map.of("database", "logic_db", "schema", "public", "query", "missing", "object_types", List.of("table"))));
Map<?, ?> actualEmptyState = (Map<?, ?>) actual.toPayload().get("empty_state");
@@ -233,7 +243,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithUnknownDatabase() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of("database", "missing_db", "query", "orders")));
Map<?, ?> actualEmptyState = (Map<?, ?>) actual.toPayload().get("empty_state");
assertThat(actualEmptyState.get("category"), is("unknown_database"));
@@ -243,7 +254,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithSchemaNotVisible() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1",
Map.of("database", "logic_db", "schema", "missing_schema", "object_types", List.of("table"))));
Map<?, ?> actualEmptyState = (Map<?, ?>) actual.toPayload().get("empty_state");
@@ -264,7 +276,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithAmbiguityGuidance() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext(createDuplicatedTableMetadata()))) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope(createDuplicatedTableMetadata())) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of("query", "orders")));
Map<String, Object> actualPayload = actual.toPayload();
Map<?, ?> actualAmbiguityState = (Map<?, ?>) actualPayload.get("ambiguity_state");
@@ -282,7 +295,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithPrefixNameAmbiguity() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext(createDuplicatedTableMetadata()))) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope(createDuplicatedTableMetadata())) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of("query", "order")));
Map<?, ?> actualAmbiguityState = (Map<?, ?>) actual.toPayload().get("ambiguity_state");
assertTrue((Boolean) actualAmbiguityState.get("ambiguous"));
@@ -293,9 +307,9 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithoutUnrelatedChildAmbiguity() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext())) {
- MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1",
- Map.of("database", "logic_db", "schema", "public", "query", "orders")));
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope()) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
+ MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of("database", "logic_db", "schema", "public", "query", "orders")));
Map<String, Object> actualPayload = actual.toPayload();
assertThat(((List<?>) actualPayload.get("items")).size(), is(5));
assertFalse(actualPayload.containsKey("ambiguity_state"));
@@ -305,7 +319,8 @@ class SearchMetadataToolHandlerTest {
@Test
void assertHandleSearchMetadataWithAmbiguityAcrossCompleteResult() {
- try (MCPRequestScope requestContext = new MCPRequestScope(createSearchRuntimeContext(createDuplicatedTableMetadata()))) {
+ try (RequestScopeFixture requestScopeFixture = createSearchRequestScope(createDuplicatedTableMetadata())) {
+ MCPRequestScope requestContext = requestScopeFixture.getRequestScope();
MCPResponse actual = new SearchMetadataToolHandler().handle(requestContext, new MCPToolCall("session-1", Map.of("query", "orders")));
Map<String, Object> actualPayload = actual.toPayload();
assertThat(((List<?>) actualPayload.get("items")).size(), is(3));
@@ -320,10 +335,6 @@ class SearchMetadataToolHandlerTest {
}
}
- private MCPRuntimeContext createSearchRuntimeContext() {
- return createSearchRuntimeContext(ResourceTestDataFactory.createDatabaseMetadata());
- }
-
private MCPRuntimeContext createSearchRuntimeContext(final List<DatabaseMetadataFixture> databaseMetadata) {
MCPRuntimeContext result = ResourceTestDataFactory.createRuntimeContext(databaseMetadata);
result.getSessionManager().createSession("session-1");
@@ -331,7 +342,10 @@ class SearchMetadataToolHandlerTest {
}
private RequestScopeFixture createSearchRequestScope() {
- List<DatabaseMetadataFixture> databaseMetadata = ResourceTestDataFactory.createDatabaseMetadata();
+ return createSearchRequestScope(ResourceTestDataFactory.createDatabaseMetadata());
+ }
+
+ private RequestScopeFixture createSearchRequestScope(final List<DatabaseMetadataFixture> databaseMetadata) {
return ResourceTestDataFactory.createRequestScopeFixture(createSearchRuntimeContext(databaseMetadata), databaseMetadata);
}
@@ -340,10 +354,7 @@ class SearchMetadataToolHandlerTest {
}
private List<DatabaseMetadataFixture> createLargeDatabaseMetadata() {
- List<ResourceTestDataFactory.TableMetadataFixture> tables = new LinkedList<>();
- for (int index = 0; index < 101; index++) {
- tables.add(ResourceTestDataFactory.createTable("table_" + index, List.of(), List.of()));
- }
+ List<TableMetadataFixture> tables = IntStream.range(0, 101).mapToObj(index -> ResourceTestDataFactory.createTable("table_" + index, List.of(), List.of())).collect(Collectors.toList());
return List.of(ResourceTestDataFactory.createDatabase("large_db", "MySQL", "", List.of(ResourceTestDataFactory.createSchema("public", tables, List.of(), List.of()))));
}
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryServiceTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryServiceTest.java
index e90d84b3d28..9b207d6cf2c 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryServiceTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/workflow/WorkflowProxyQueryServiceTest.java
@@ -23,23 +23,22 @@ import org.apache.shardingsphere.database.connector.core.metadata.database.metad
import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.schema.DialectSchemaSemantics;
import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
-import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeFactory;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnavailableException;
import org.apache.shardingsphere.mcp.core.session.MCPSessionManager;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapabilityProvider;
+import org.apache.shardingsphere.mcp.support.database.exception.DatabaseCapabilityNotFoundException;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import java.sql.Connection;
-import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -47,8 +46,6 @@ import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
@@ -116,6 +113,12 @@ class WorkflowProxyQueryServiceTest {
assertThat(actual.getMessage(), is("Database `logic_db` is not configured."));
}
+ @Test
+ void assertGetDatabaseTypeWithoutCapability() {
+ WorkflowProxyQueryService service = new WorkflowProxyQueryService(new MCPSessionManager(Map.of()), mock(MCPDatabaseCapabilityProvider.class));
+ assertThrows(DatabaseCapabilityNotFoundException.class, () -> service.getDatabaseType("logic_db"));
+ }
+
@Test
void assertQueryColumnDefinitionFormatsDecimalDefinition() throws SQLException {
RuntimeDatabaseConfiguration runtimeDatabaseConfig = mock(RuntimeDatabaseConfiguration.class);
@@ -205,21 +208,11 @@ class WorkflowProxyQueryServiceTest {
}
private WorkflowProxyQueryService createService(final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases, final String databaseType) {
- Map<String, RuntimeDatabaseConfiguration> capabilityRuntimeDatabases = new LinkedHashMap<>(runtimeDatabases.isEmpty() ? 0 : 1, 1F);
- if (!runtimeDatabases.isEmpty()) {
- capabilityRuntimeDatabases.put("logic_db", createCapabilityRuntimeDatabaseConfiguration());
- }
- try (MockedStatic<DatabaseTypeFactory> ignored = mockDatabaseTypeFactory(databaseType)) {
- return new WorkflowProxyQueryService(new MCPSessionManager(runtimeDatabases), new MCPDatabaseCapabilityProvider(capabilityRuntimeDatabases));
- }
- }
-
- private MockedStatic<DatabaseTypeFactory> mockDatabaseTypeFactory(final String databaseType) {
- MockedStatic<DatabaseTypeFactory> result = mockStatic(DatabaseTypeFactory.class, CALLS_REAL_METHODS);
- DatabaseType databaseTypeFromMetadata = mock(DatabaseType.class);
- when(databaseTypeFromMetadata.getType()).thenReturn(databaseType);
- result.when(() -> DatabaseTypeFactory.get(any(DatabaseMetaData.class))).thenReturn(databaseTypeFromMetadata);
- return result;
+ MCPDatabaseCapability databaseCapability = mock(MCPDatabaseCapability.class);
+ when(databaseCapability.getDatabaseType()).thenReturn(databaseType);
+ MCPDatabaseCapabilityProvider databaseCapabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
+ when(databaseCapabilityProvider.provide("logic_db")).thenReturn(Optional.of(databaseCapability));
+ return new WorkflowProxyQueryService(new MCPSessionManager(runtimeDatabases), databaseCapabilityProvider);
}
private void mockDialectDatabaseMetaData(final String databaseType, final QuoteCharacter quoteCharacter, final DialectSchemaSemantics schemaSemantics,
@@ -234,25 +227,4 @@ class WorkflowProxyQueryServiceTest {
databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseTypeFromSPI)).thenReturn(Optional.of(dialectDatabaseMetaData));
}
- private RuntimeDatabaseConfiguration createCapabilityRuntimeDatabaseConfiguration() {
- RuntimeDatabaseConfiguration result = mock(RuntimeDatabaseConfiguration.class);
- Connection connection = mock(Connection.class);
- DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
- try {
- when(result.openConnection("logic_db")).thenReturn(connection);
- when(connection.getMetaData()).thenReturn(databaseMetaData);
- when(databaseMetaData.getDatabaseProductVersion()).thenReturn("");
- mockEmptyScalarQueries(connection);
- } catch (final SQLException ex) {
- throw new IllegalStateException(ex);
- }
- return result;
- }
-
- private void mockEmptyScalarQueries(final Connection connection) throws SQLException {
- Statement statement = mock(Statement.class);
- ResultSet resultSet = mock(ResultSet.class);
- when(connection.createStatement()).thenReturn(statement);
- when(statement.executeQuery(anyString())).thenReturn(resultSet);
- }
}
diff --git a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/resource/handler/BroadcastTableRuleHandlerTest.java b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/resource/handler/BroadcastTableRuleHandlerTest.java
index f9bb7fcca43..ca26d50e7bc 100644
--- a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/resource/handler/BroadcastTableRuleHandlerTest.java
+++ b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/resource/handler/BroadcastTableRuleHandlerTest.java
@@ -23,14 +23,19 @@ import org.apache.shardingsphere.mcp.feature.broadcast.BroadcastFeatureDefinitio
import org.apache.shardingsphere.mcp.feature.broadcast.tool.service.BroadcastRuleInspectionService;
import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
import java.util.List;
import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -54,12 +59,17 @@ class BroadcastTableRuleHandlerTest {
when(databaseContext.getQueryFacade()).thenReturn(queryFacade);
when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
when(ruleInspectionService.queryBroadcastRules(queryFacade, "logic_db")).thenReturn(List.of(Map.of("broadcast_table", "t_order"), Map.of("broadcast_table", "t_order_item")));
- MCPResponse actual = new BroadcastTableRuleHandler(ruleInspectionService).handle(databaseContext, new MCPUriVariables(Map.of("database", "logic_db", "table", "t_order")));
- verify(ruleInspectionService).queryBroadcastRules(queryFacade, "logic_db");
- List<?> items = (List<?>) actual.toPayload().get("items");
- assertThat(items.size(), is(1));
- assertThat(((Map<?, ?>) items.getFirst()).get("broadcast_table"), is("t_order"));
- assertThat(actual.toPayload().get("self_uri"), is("shardingsphere://features/broadcast/databases/logic_db/tables/t_order/rule"));
- assertThat(((Map<?, ?>) actual.toPayload().get("parent_resource")).get("uri"), is("shardingsphere://features/broadcast/databases/logic_db/rules"));
+ try (MockedStatic<WorkflowSQLUtils> workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS)) {
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier(anyString(), anyString(), anyString()))
+ .thenAnswer(invocation -> invocation.getArgument(1, String.class).equals(invocation.getArgument(2, String.class)));
+ MCPResponse actual = new BroadcastTableRuleHandler(ruleInspectionService).handle(
+ databaseContext, new MCPUriVariables(Map.of("database", "logic_db", "table", "t_order")));
+ verify(ruleInspectionService).queryBroadcastRules(queryFacade, "logic_db");
+ List<?> items = (List<?>) actual.toPayload().get("items");
+ assertThat(items.size(), is(1));
+ assertThat(((Map<?, ?>) items.getFirst()).get("broadcast_table"), is("t_order"));
+ assertThat(actual.toPayload().get("self_uri"), is("shardingsphere://features/broadcast/databases/logic_db/tables/t_order/rule"));
+ assertThat(((Map<?, ?>) actual.toPayload().get("parent_resource")).get("uri"), is("shardingsphere://features/broadcast/databases/logic_db/rules"));
+ }
}
}
diff --git a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningServiceTest.java b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningServiceTest.java
index 1075375f524..f4a3df307b6 100644
--- a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningServiceTest.java
+++ b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningServiceTest.java
@@ -23,7 +23,11 @@ import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
import java.util.List;
import java.util.Map;
@@ -32,12 +36,29 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
class BroadcastWorkflowPlanningServiceTest {
+ private MockedStatic<WorkflowSQLUtils> workflowSQLUtils;
+
+ @BeforeEach
+ void setUp() {
+ workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS);
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier(anyString(), anyString(), anyString()))
+ .thenAnswer(invocation -> invocation.getArgument(1, String.class).equals(invocation.getArgument(2, String.class)));
+ }
+
+ @AfterEach
+ void tearDown() {
+ workflowSQLUtils.close();
+ }
+
@Test
void assertPlanCreateRule() {
MCPFeatureQueryFacade queryFacade = mockQueryFacade(List.of());
diff --git a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationServiceTest.java b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationServiceTest.java
index faa7b3b00df..b4ddc1857db 100644
--- a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationServiceTest.java
+++ b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationServiceTest.java
@@ -28,9 +28,11 @@ import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
import org.apache.shardingsphere.mcp.support.workflow.model.InteractionPlan;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationException;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationSupport;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
import java.util.List;
import java.util.Map;
@@ -39,7 +41,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@@ -67,13 +72,17 @@ class BroadcastWorkflowValidationServiceTest {
when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
- Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, metadataQueryFacade, queryFacade, executionFacade, "session-1", snapshot);
- assertThat(actual.get("status"), is("validated"));
- assertThat(actual.get("overall_status"), is("passed"));
- assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("passed"));
- verifyNoInteractions(metadataQueryFacade);
- verifyNoInteractions(executionFacade);
+ try (MockedStatic<WorkflowSQLUtils> workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS)) {
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier(anyString(), anyString(), anyString()))
+ .thenAnswer(invocation -> invocation.getArgument(1, String.class).equals(invocation.getArgument(2, String.class)));
+ Map<String, Object> actual = createService(ruleInspectionService)
+ .validate(workflowSessionContext, metadataQueryFacade, queryFacade, executionFacade, "session-1", snapshot);
+ assertThat(actual.get("status"), is("validated"));
+ assertThat(actual.get("overall_status"), is("passed"));
+ assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("passed"));
+ verifyNoInteractions(metadataQueryFacade);
+ verifyNoInteractions(executionFacade);
+ }
}
@Test
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
index 307a1cffbed..8a181be2275 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningService.java
@@ -20,7 +20,6 @@ package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
import org.apache.shardingsphere.mcp.feature.encrypt.EncryptFeatureDefinition;
import org.apache.shardingsphere.mcp.feature.encrypt.tool.model.EncryptWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.encrypt.tool.model.EncryptWorkflowState;
-import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
@@ -100,11 +99,11 @@ public final class EncryptWorkflowPlanningService {
if (!planningSupport.ensureSupportedOperationType(clarifiedIntent, SUPPORTED_OPERATION_TYPES, result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
}
- if (!planningSupport.ensurePlanningContext(metadataQueryFacade, mergedRequest, clarifiedIntent, result)) {
+ if (!planningSupport.ensurePlanningContext(metadataQueryFacade, queryFacade, mergedRequest, clarifiedIntent, result)) {
String currentStep = WorkflowLifecycle.STATUS_FAILED.equals(result.getStatus()) ? WorkflowLifecycle.STEP_FAILED : WorkflowLifecycle.STEP_CLARIFYING;
return workflowSessionContext.persist(result, currentStep, result.getStatus());
}
- String databaseType = metadataQueryFacade.queryDatabase(WorkflowSQLUtils.normalizeIdentifier(mergedRequest.getDatabase())).map(RuntimeDatabaseProfile::getDatabaseType).orElse("");
+ String databaseType = queryFacade.getDatabaseType(mergedRequest.getDatabase());
List<Map<String, Object>> existingRules = ruleInspectionService.queryEncryptRules(queryFacade, mergedRequest.getDatabase(), mergedRequest.getTable());
if (!ensureLifecycleState(clarifiedIntent, mergedRequest, existingRules, result, databaseType)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
index a3cd988dd37..3ddb0b78984 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowPlanningServiceTest.java
@@ -18,10 +18,14 @@
package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
+import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mcp.feature.encrypt.TestWorkflowSessionContext;
import org.apache.shardingsphere.mcp.feature.encrypt.tool.model.EncryptWorkflowRequest;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
@@ -35,10 +39,13 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanPayloadBuilder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
import java.lang.reflect.Field;
@@ -53,16 +60,39 @@ import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
class EncryptWorkflowPlanningServiceTest {
+ private MockedStatic<TypedSPILoader> typedSPILoader;
+
+ private MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader;
+
+ @BeforeEach
+ void setUp() {
+ typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class);
+ DatabaseType databaseType = mock(DatabaseType.class);
+ typedSPILoader.when(() -> TypedSPILoader.findService(DatabaseType.class, "FixtureDB")).thenReturn(Optional.of(databaseType));
+ DialectDatabaseMetaData dialectDatabaseMetaData = mock(DialectDatabaseMetaData.class);
+ when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.KEEP_ORIGIN);
+ databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType)).thenReturn(Optional.of(dialectDatabaseMetaData));
+ }
+
+ @AfterEach
+ void tearDown() {
+ databaseTypedSPILoader.close();
+ typedSPILoader.close();
+ }
+
@Test
void assertPlanRejectsMissingPlanningContext() {
EncryptWorkflowPlanningService service = createService(mock(EncryptRuleInspectionService.class), mock(EncryptAlgorithmRecommendationService.class),
mock(EncryptAlgorithmPropertyTemplateService.class), mock(EncryptRuleDistSQLPlanningService.class));
- WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), "session-1",
+ WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), mock(MCPMetadataQueryFacade.class), createQueryFacade(), "session-1",
new EncryptWorkflowRequest());
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.DATABASE_REQUIRED));
@@ -74,7 +104,7 @@ class EncryptWorkflowPlanningServiceTest {
mock(EncryptAlgorithmPropertyTemplateService.class), mock(EncryptRuleDistSQLPlanningService.class));
EncryptWorkflowRequest request = new EncryptWorkflowRequest();
request.setDatabase("logic_db");
- WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("clarifying"));
assertTrue(actual.getClarifiedIntent().getClarificationMessages().contains("Please specify target table."));
assertTrue(actual.getClarifiedIntent().getClarificationMessages().contains("Please specify target column."));
@@ -88,7 +118,7 @@ class EncryptWorkflowPlanningServiceTest {
when(metadataQueryFacade.queryTableColumn(any(), any(), any(), any())).thenReturn(Optional.empty());
EncryptWorkflowPlanningService service = createService(mock(EncryptRuleInspectionService.class), mock(EncryptAlgorithmRecommendationService.class),
mock(EncryptAlgorithmPropertyTemplateService.class), mock(EncryptRuleDistSQLPlanningService.class));
- WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), metadataQueryFacade, mock(MCPFeatureQueryFacade.class), "session-1", createRequest("create"));
+ WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), metadataQueryFacade, createQueryFacade(), "session-1", createRequest("create"));
assertThat(actual.getStatus(), is("failed"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.COLUMN_NOT_FOUND));
}
@@ -100,7 +130,7 @@ class EncryptWorkflowPlanningServiceTest {
EncryptWorkflowPlanningService service = createService(ruleInspectionService, mock(EncryptAlgorithmRecommendationService.class),
mock(EncryptAlgorithmPropertyTemplateService.class), mock(EncryptRuleDistSQLPlanningService.class));
EncryptWorkflowRequest request = createRequest("create");
- WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("failed"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.RULE_STATE_MISMATCH));
}
@@ -113,7 +143,7 @@ class EncryptWorkflowPlanningServiceTest {
when(ruleDistSQLPlanningService.planEncryptDropRule(any())).thenReturn(List.of(new RuleArtifact("drop", "DROP ENCRYPT RULE `orders`")));
WorkflowContextSnapshot actual = createService(ruleInspectionService, mock(EncryptAlgorithmRecommendationService.class),
mock(EncryptAlgorithmPropertyTemplateService.class), ruleDistSQLPlanningService)
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", createRequest("drop"));
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", createRequest("drop"));
assertThat(actual.getStatus(), is("planned"));
assertThat(actual.getRuleArtifacts().size(), is(1));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.ENCRYPT_DROP_SCOPE_LIMITED));
@@ -133,7 +163,7 @@ class EncryptWorkflowPlanningServiceTest {
request.getOptions().setCipherColumnName("phone_cipher");
request.getPrimaryAlgorithmProperties().put("aes-key-value", "123456");
WorkflowContextSnapshot actual = createService(ruleInspectionService, new EncryptAlgorithmRecommendationService(), new EncryptAlgorithmPropertyTemplateService(),
- new EncryptRuleDistSQLPlanningService()).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ new EncryptRuleDistSQLPlanningService()).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getClarifiedIntent().getOperationType(), is(expectedOperationType));
assertThat(actual.getClarifiedIntent().getFieldSemantics(), is(expectedFieldSemantics));
assertThat(actual.getStatus(), is(expectedStatus));
@@ -154,7 +184,7 @@ class EncryptWorkflowPlanningServiceTest {
return List.of();
});
WorkflowContextSnapshot actual = createService(ruleInspectionService, algorithmRecommendationService, mock(EncryptAlgorithmPropertyTemplateService.class),
- mock(EncryptRuleDistSQLPlanningService.class)).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1",
+ mock(EncryptRuleDistSQLPlanningService.class)).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1",
createRequest("create"));
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getClarifiedIntent().getClarificationMessages().getFirst(), is("Please use an encrypt algorithm that is visible in the current Proxy and satisfies the requirements."));
@@ -195,7 +225,7 @@ class EncryptWorkflowPlanningServiceTest {
request.getOptions().setRequiresLikeQuery(true);
request.getOptions().setLikeQueryColumnName("phone_like_query");
WorkflowContextSnapshot actual = createService(ruleInspectionService, new EncryptAlgorithmRecommendationService(), propertyTemplateService, new EncryptRuleDistSQLPlanningService())
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("planned"));
assertThat(((EncryptWorkflowRequest) actual.getRequest()).getOptions().getLikeQueryAlgorithmType(), is("FPE"));
}
@@ -219,7 +249,7 @@ class EncryptWorkflowPlanningServiceTest {
request.setColumn("name");
request.getOptions().setCipherColumnName("name_cipher");
WorkflowContextSnapshot actual = createService(ruleInspectionService, createPrimaryCandidateRecommendation(), new EncryptAlgorithmPropertyTemplateService(),
- new EncryptRuleDistSQLPlanningService()).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ new EncryptRuleDistSQLPlanningService()).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
String actualSQL = actual.getRuleArtifacts().getFirst().getSql();
assertThat(actual.getStatus(), is("planned"));
assertTrue(actualSQL.startsWith("CREATE ENCRYPT RULE `t_user`"));
@@ -237,7 +267,7 @@ class EncryptWorkflowPlanningServiceTest {
EncryptWorkflowRequest request = createRequest("create");
request.setColumn("phone");
WorkflowContextSnapshot actual = createService(ruleInspectionService, createPrimaryCandidateRecommendation(), createEmptyPropertyTemplateService(), new EncryptRuleDistSQLPlanningService())
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.ENCRYPT_RULE_REWRITE_LIMITED));
assertTrue(actual.getRuleArtifacts().isEmpty());
@@ -251,7 +281,7 @@ class EncryptWorkflowPlanningServiceTest {
Map.of("logic_column", "email", "cipher_column", "email_cipher", "encryptor_type", "AES", "encryptor_props", "aes-key-value=old")));
WorkflowContextSnapshot actual = createService(ruleInspectionService, mock(EncryptAlgorithmRecommendationService.class),
mock(EncryptAlgorithmPropertyTemplateService.class), new EncryptRuleDistSQLPlanningService())
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", createRequest("drop"));
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", createRequest("drop"));
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.ENCRYPT_RULE_REWRITE_LIMITED));
assertTrue(actual.getRuleArtifacts().isEmpty());
@@ -262,7 +292,7 @@ class EncryptWorkflowPlanningServiceTest {
EncryptWorkflowRequest request = createRequest("replace");
WorkflowContextSnapshot actual = createService(mock(EncryptRuleInspectionService.class), mock(EncryptAlgorithmRecommendationService.class),
mock(EncryptAlgorithmPropertyTemplateService.class), mock(EncryptRuleDistSQLPlanningService.class))
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("failed"));
assertThat(actual.getClarifiedIntent().getOperationType(), is(""));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
@@ -277,7 +307,7 @@ class EncryptWorkflowPlanningServiceTest {
EncryptAlgorithmPropertyTemplateService propertyTemplateService = mock(EncryptAlgorithmPropertyTemplateService.class);
when(propertyTemplateService.findRequirements(any(), any(), any())).thenReturn(List.of(new AlgorithmPropertyRequirement("primary", "aes-key-value", true, true, "key", "")));
WorkflowContextSnapshot actual = createService(ruleInspectionService, createPrimaryCandidateRecommendation(), propertyTemplateService, mock(EncryptRuleDistSQLPlanningService.class))
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", createRequestWithoutProperties());
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", createRequestWithoutProperties());
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.REQUIRED_PROPERTY_MISSING));
assertThat(actual.getClarifiedIntent().getClarificationMessages().getFirst(), is("Please provide property `aes-key-value`."));
@@ -287,7 +317,7 @@ class EncryptWorkflowPlanningServiceTest {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of());
return createService(ruleInspectionService, createPrimaryCandidateRecommendation(), createEmptyPropertyTemplateService(), new EncryptRuleDistSQLPlanningService())
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
}
private EncryptAlgorithmRecommendationService createPrimaryCandidateRecommendation() {
@@ -330,6 +360,12 @@ class EncryptWorkflowPlanningServiceTest {
return result;
}
+ private MCPFeatureQueryFacade createQueryFacade() {
+ MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
+ when(result.getDatabaseType(any())).thenReturn("FixtureDB");
+ return result;
+ }
+
private MCPMetadataQueryFacade createMetadataQueryFacade() {
MCPMetadataQueryFacade result = mock(MCPMetadataQueryFacade.class);
when(result.queryDatabase(any())).thenReturn(Optional.of(createDatabaseMetadata()));
diff --git a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java
index 99c51c50691..d6217e230b9 100644
--- a/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java
+++ b/mcp/features/encrypt/src/test/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationServiceTest.java
@@ -17,6 +17,10 @@
package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
+import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mcp.feature.encrypt.EncryptFeatureDefinition;
@@ -38,6 +42,8 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactPa
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationException;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
@@ -45,6 +51,7 @@ import org.mockito.internal.configuration.plugins.Plugins;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -52,6 +59,7 @@ 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.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -59,12 +67,33 @@ import static org.mockito.Mockito.when;
class EncryptWorkflowValidationServiceTest {
+ private MockedStatic<TypedSPILoader> typedSPILoader;
+
+ private MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader;
+
+ @BeforeEach
+ void setUp() {
+ typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class);
+ DatabaseType databaseType = mock(DatabaseType.class);
+ typedSPILoader.when(() -> TypedSPILoader.findService(DatabaseType.class, "FixtureDB")).thenReturn(Optional.of(databaseType));
+ DialectDatabaseMetaData dialectDatabaseMetaData = mock(DialectDatabaseMetaData.class);
+ when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.KEEP_ORIGIN);
+ databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType)).thenReturn(Optional.of(dialectDatabaseMetaData));
+ }
+
+ @AfterEach
+ void tearDown() {
+ databaseTypedSPILoader.close();
+ typedSPILoader.close();
+ }
+
@Test
void assertValidateRejectsDifferentSession() {
WorkflowSessionContext workflowSessionContext = new TestWorkflowSessionContext();
workflowSessionContext.save(createSnapshot("plan-1", "session-1", "executed", "create"));
Map<String, Object> actual = createService(mock(EncryptRuleInspectionService.class))
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-2",
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-2",
workflowSessionContext.getRequired("plan-1"));
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("issues")).getFirst()).get("code"), is(WorkflowIssueCode.SESSION_OWNERSHIP_MISMATCH));
@@ -77,8 +106,7 @@ class EncryptWorkflowValidationServiceTest {
workflowSessionContext.save(snapshot);
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of(createRuleRow()));
- MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
- when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
+ MCPFeatureQueryFacade queryFacade = createQueryFacade();
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
Map<String, Object> actual = createService(ruleInspectionService)
@@ -124,15 +152,13 @@ class EncryptWorkflowValidationServiceTest {
snapshot.setRequest(request);
String sql = "CREATE ENCRYPT RULE `t_user` (COLUMNS((NAME=`phone`, CIPHER=`phone_cipher`, "
+ "ENCRYPT_ALGORITHM(TYPE(NAME='aes', PROPERTIES('aes-key-value'='******', 'digest-algorithm-name'='SHA-1'))))))";
- try (MockedStatic<TypedSPILoader> ignored = mockStatic(TypedSPILoader.class)) {
- ignored.when(() -> TypedSPILoader.checkService(EncryptAlgorithm.class, "AES", WorkflowSQLUtils.createProperties(request.getPrimaryAlgorithmProperties())))
- .thenThrow(new IllegalArgumentException("raw-secret"));
- List<Map<String, Object>> actual = new EncryptWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact(sql, sql)));
- assertThat(actual.size(), is(1));
- assertThat(actual.getFirst().get("message"), is("Generated encrypt DistSQL references encrypt algorithm `AES`, "
- + "but it cannot be loaded or initialized by EncryptAlgorithm SPI."));
- assertFalse(String.valueOf(actual).contains("raw-secret"));
- }
+ typedSPILoader.when(() -> TypedSPILoader.checkService(EncryptAlgorithm.class, "AES", WorkflowSQLUtils.createProperties(request.getPrimaryAlgorithmProperties())))
+ .thenThrow(new IllegalArgumentException("raw-secret"));
+ List<Map<String, Object>> actual = new EncryptWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact(sql, sql)));
+ assertThat(actual.size(), is(1));
+ assertThat(actual.getFirst().get("message"), is("Generated encrypt DistSQL references encrypt algorithm `AES`, "
+ + "but it cannot be loaded or initialized by EncryptAlgorithm SPI."));
+ assertFalse(String.valueOf(actual).contains("raw-secret"));
}
@Test
@@ -150,7 +176,7 @@ class EncryptWorkflowValidationServiceTest {
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of(createRuleRow()));
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
- createService(ruleInspectionService).synchronize(snapshot, metadataQueryFacade, mock(MCPFeatureQueryFacade.class), executionFacade, "session-1");
+ createService(ruleInspectionService).synchronize(snapshot, metadataQueryFacade, createQueryFacade(), executionFacade, "session-1");
verifyNoInteractions(metadataQueryFacade);
verifyNoInteractions(executionFacade);
}
@@ -161,7 +187,7 @@ class EncryptWorkflowValidationServiceTest {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of());
WorkflowSynchronizationException actual = assertThrows(WorkflowSynchronizationException.class,
- () -> createService(ruleInspectionService).synchronize(snapshot, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
+ () -> createService(ruleInspectionService).synchronize(snapshot, mock(MCPMetadataQueryFacade.class), createQueryFacade(),
mock(MCPFeatureExecutionFacade.class), "session-1"));
assertThat(actual.getIssueCode(), is(WorkflowIssueCode.RULE_STATE_MISMATCH));
}
@@ -174,7 +200,7 @@ class EncryptWorkflowValidationServiceTest {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of());
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("validated"));
assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("passed"));
}
@@ -187,7 +213,7 @@ class EncryptWorkflowValidationServiceTest {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of());
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(actual.get("overall_status"), is("failed"));
}
@@ -203,7 +229,7 @@ class EncryptWorkflowValidationServiceTest {
"cipher_column", "phone_cipher_old",
"encryptor_type", "AES")));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("expected"), is("cipher_column=phone_cipher"));
@@ -227,7 +253,7 @@ class EncryptWorkflowValidationServiceTest {
"encryptor_type", "AES",
"encryptor_props", Map.of("aes-key-value", "old-key"))));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertFalse(String.valueOf(actual.get("mismatches")).contains("123456"));
assertFalse(String.valueOf(actual.get("mismatches")).contains("old-key"));
@@ -253,7 +279,7 @@ class EncryptWorkflowValidationServiceTest {
"encryptor_type", "AES",
"encryptor_props", Map.of("aes-key-value", "raw-actual-secret"))));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("validated"));
assertThat(actual.get("overall_status"), is("passed"));
assertThat(((List<?>) actual.get("mismatches")).size(), is(0));
@@ -280,7 +306,7 @@ class EncryptWorkflowValidationServiceTest {
"encryptor_type", "AES",
"encryptor_props", Map.of("aes-key-value", "secret_reference:primary.aes-key-value"))));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("expected"), is("encryptor_props={aes-key-value=******}"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("actual"), is("encryptor_props={aes-key-value=******}"));
@@ -298,7 +324,7 @@ class EncryptWorkflowValidationServiceTest {
EncryptRuleInspectionService ruleInspectionService = mock(EncryptRuleInspectionService.class);
when(ruleInspectionService.queryEncryptRules(any(), any(), any())).thenReturn(List.of(createRuleRow()));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("expected"), is("logic_column=email"));
}
@@ -314,11 +340,17 @@ class EncryptWorkflowValidationServiceTest {
createRuleRow(),
Map.of("logic_column", "email", "cipher_column", "email_cipher", "encryptor_type", "AES")));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("actual"), is("logic_column=email"));
}
+ private MCPFeatureQueryFacade createQueryFacade() {
+ MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
+ when(result.getDatabaseType(any())).thenReturn("FixtureDB");
+ return result;
+ }
+
private EncryptWorkflowValidationService createService(final EncryptRuleInspectionService ruleInspectionService) {
EncryptWorkflowValidationService result = new EncryptWorkflowValidationService();
try {
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
index f25092ecdf3..c79e3fed4d4 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningService.java
@@ -98,7 +98,7 @@ public final class MaskWorkflowPlanningService {
if (!planningSupport.ensureSupportedOperationType(clarifiedIntent, SUPPORTED_OPERATION_TYPES, result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
}
- if (!planningSupport.ensurePlanningContext(metadataQueryFacade, mergedRequest, clarifiedIntent, result)) {
+ if (!planningSupport.ensurePlanningContext(metadataQueryFacade, queryFacade, mergedRequest, clarifiedIntent, result)) {
String currentStep = WorkflowLifecycle.STATUS_FAILED.equals(result.getStatus()) ? WorkflowLifecycle.STEP_FAILED : WorkflowLifecycle.STEP_CLARIFYING;
return workflowSessionContext.persist(result, currentStep, result.getStatus());
}
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
index d71bbc24789..201ea238be8 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowPlanningServiceTest.java
@@ -18,10 +18,14 @@
package org.apache.shardingsphere.mcp.feature.mask.tool.service;
import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
+import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mcp.feature.mask.TestWorkflowSessionContext;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
@@ -36,10 +40,13 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanPayloadBuilder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
import java.lang.reflect.Field;
@@ -54,17 +61,40 @@ import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
class MaskWorkflowPlanningServiceTest {
+ private MockedStatic<TypedSPILoader> typedSPILoader;
+
+ private MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader;
+
+ @BeforeEach
+ void setUp() {
+ typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class);
+ DatabaseType databaseType = mock(DatabaseType.class);
+ typedSPILoader.when(() -> TypedSPILoader.findService(DatabaseType.class, "FixtureDB")).thenReturn(Optional.of(databaseType));
+ DialectDatabaseMetaData dialectDatabaseMetaData = mock(DialectDatabaseMetaData.class);
+ when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.KEEP_ORIGIN);
+ databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType)).thenReturn(Optional.of(dialectDatabaseMetaData));
+ }
+
+ @AfterEach
+ void tearDown() {
+ databaseTypedSPILoader.close();
+ typedSPILoader.close();
+ }
+
@Test
void assertPlanRejectsMissingPlanningContext() {
WorkflowSessionContext workflowSessionContext = new TestWorkflowSessionContext();
WorkflowContextSnapshot actual = createService(mock(MaskRuleInspectionService.class), mock(MaskAlgorithmRecommendationService.class),
mock(MaskAlgorithmPropertyTemplateService.class), mock(MaskRuleDistSQLPlanningService.class))
- .plan(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), "session-1", new WorkflowRequest());
+ .plan(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), "session-1", new WorkflowRequest());
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.DATABASE_REQUIRED));
}
@@ -75,7 +105,7 @@ class MaskWorkflowPlanningServiceTest {
request.setDatabase("logic_db");
WorkflowContextSnapshot actual = createService(mock(MaskRuleInspectionService.class), mock(MaskAlgorithmRecommendationService.class),
mock(MaskAlgorithmPropertyTemplateService.class), mock(MaskRuleDistSQLPlanningService.class))
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("clarifying"));
assertTrue(actual.getClarifiedIntent().getClarificationMessages().contains("Please specify target table."));
assertTrue(actual.getClarifiedIntent().getClarificationMessages().contains("Please specify target column."));
@@ -89,7 +119,7 @@ class MaskWorkflowPlanningServiceTest {
when(metadataQueryFacade.queryTableColumn(any(), any(), any(), any())).thenReturn(Optional.empty());
WorkflowContextSnapshot actual = createService(mock(MaskRuleInspectionService.class), mock(MaskAlgorithmRecommendationService.class),
mock(MaskAlgorithmPropertyTemplateService.class), mock(MaskRuleDistSQLPlanningService.class))
- .plan(new TestWorkflowSessionContext(), metadataQueryFacade, mock(MCPFeatureQueryFacade.class), "session-1", createRequest("create"));
+ .plan(new TestWorkflowSessionContext(), metadataQueryFacade, createQueryFacade(), "session-1", createRequest("create"));
assertThat(actual.getStatus(), is("failed"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.COLUMN_NOT_FOUND));
}
@@ -98,8 +128,7 @@ class MaskWorkflowPlanningServiceTest {
void assertPlanRejectsLifecycleMismatchForCreate() {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of(Map.of("column", "phone")));
- MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
- when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
+ MCPFeatureQueryFacade queryFacade = createQueryFacade();
WorkflowRequest request = createRequest("create");
WorkflowContextSnapshot actual = createService(ruleInspectionService, mock(MaskAlgorithmRecommendationService.class),
mock(MaskAlgorithmPropertyTemplateService.class), mock(MaskRuleDistSQLPlanningService.class))
@@ -118,7 +147,7 @@ class MaskWorkflowPlanningServiceTest {
WorkflowRequest request = createRequest("create");
request.setColumn("amount");
request.getPrimaryAlgorithmProperties().put("from-x", "1");
- WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ WorkflowContextSnapshot actual = service.plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.MASK_RULE_REWRITE_LIMITED));
assertThat(actual.getRuleArtifacts().size(), is(0));
@@ -129,7 +158,7 @@ class MaskWorkflowPlanningServiceTest {
WorkflowRequest request = createRequest("replace");
WorkflowContextSnapshot actual = createService(mock(MaskRuleInspectionService.class), mock(MaskAlgorithmRecommendationService.class),
mock(MaskAlgorithmPropertyTemplateService.class), mock(MaskRuleDistSQLPlanningService.class))
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("failed"));
assertThat(actual.getClarifiedIntent().getOperationType(), is(""));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.WORKFLOW_STATUS_INVALID));
@@ -145,7 +174,7 @@ class MaskWorkflowPlanningServiceTest {
when(ruleDistSQLPlanningService.planMaskDropRule(any())).thenReturn(new RuleArtifact("drop", "DROP MASK RULE `orders`"));
WorkflowContextSnapshot actual = createService(ruleInspectionService, mock(MaskAlgorithmRecommendationService.class),
mock(MaskAlgorithmPropertyTemplateService.class), ruleDistSQLPlanningService)
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", createRequest("drop"));
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", createRequest("drop"));
assertThat(actual.getStatus(), is("planned"));
assertThat(actual.getRuleArtifacts().size(), is(1));
}
@@ -154,8 +183,7 @@ class MaskWorkflowPlanningServiceTest {
void assertPlanRejectsDropWithRemainingTableRules() {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of(Map.of("column", "phone"), Map.of("column", "email")));
- MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
- when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
+ MCPFeatureQueryFacade queryFacade = createQueryFacade();
WorkflowContextSnapshot actual = createService(ruleInspectionService, mock(MaskAlgorithmRecommendationService.class),
mock(MaskAlgorithmPropertyTemplateService.class), mock(MaskRuleDistSQLPlanningService.class))
.plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), queryFacade, "session-1", createRequest("drop"));
@@ -173,7 +201,7 @@ class MaskWorkflowPlanningServiceTest {
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(ruleExists ? List.of(Map.of("column", "phone")) : List.of());
when(ruleInspectionService.queryMaskAlgorithms(any())).thenReturn(List.of(Map.of("type", "MASK_FROM_X_TO_Y")));
WorkflowContextSnapshot actual = createService(ruleInspectionService, new MaskAlgorithmRecommendationService(), new MaskAlgorithmPropertyTemplateService(),
- new MaskRuleDistSQLPlanningService()).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1",
+ new MaskRuleDistSQLPlanningService()).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1",
createNaturalLanguageRequest(naturalLanguageIntent));
assertThat(actual.getClarifiedIntent().getOperationType(), is(expectedOperationType));
assertThat(actual.getClarifiedIntent().getFieldSemantics(), is(expectedFieldSemantics));
@@ -195,7 +223,7 @@ class MaskWorkflowPlanningServiceTest {
return List.of();
});
WorkflowContextSnapshot actual = createService(ruleInspectionService, algorithmRecommendationService, mock(MaskAlgorithmPropertyTemplateService.class),
- mock(MaskRuleDistSQLPlanningService.class)).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1",
+ mock(MaskRuleDistSQLPlanningService.class)).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1",
createRequest("create"));
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getClarifiedIntent().getClarificationMessages().getFirst(), is("Please use a mask algorithm visible in the current Proxy."));
@@ -206,7 +234,7 @@ class MaskWorkflowPlanningServiceTest {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of());
WorkflowContextSnapshot actual = createService(ruleInspectionService, createPrimaryCandidateRecommendation(), createRequiredPropertyTemplateService(),
- mock(MaskRuleDistSQLPlanningService.class)).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1",
+ mock(MaskRuleDistSQLPlanningService.class)).plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1",
createRequest("create"));
assertThat(actual.getStatus(), is("clarifying"));
assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.REQUIRED_PROPERTY_MISSING));
@@ -219,7 +247,7 @@ class MaskWorkflowPlanningServiceTest {
WorkflowRequest request = createRequest("create");
request.getPrimaryAlgorithmProperties().put("from-x", "1");
WorkflowContextSnapshot actual = createService(ruleInspectionService, createPrimaryCandidateRecommendation(), createEmptyPropertyTemplateService(), new MaskRuleDistSQLPlanningService())
- .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), mock(MCPFeatureQueryFacade.class), "session-1", request);
+ .plan(new TestWorkflowSessionContext(), createMetadataQueryFacade(), createQueryFacade(), "session-1", request);
assertThat(actual.getStatus(), is("planned"));
assertThat(actual.getRuleArtifacts().size(), is(1));
}
@@ -262,6 +290,12 @@ class MaskWorkflowPlanningServiceTest {
return result;
}
+ private MCPFeatureQueryFacade createQueryFacade() {
+ MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
+ when(result.getDatabaseType(any())).thenReturn("FixtureDB");
+ return result;
+ }
+
private MCPMetadataQueryFacade createMetadataQueryFacade() {
MCPMetadataQueryFacade result = mock(MCPMetadataQueryFacade.class);
when(result.queryDatabase(any())).thenReturn(Optional.of(createDatabaseMetadata()));
diff --git a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java
index 7847753104b..e2394eea71f 100644
--- a/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java
+++ b/mcp/features/mask/src/test/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationServiceTest.java
@@ -17,6 +17,10 @@
package org.apache.shardingsphere.mcp.feature.mask.tool.service;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
+import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mask.spi.MaskAlgorithm;
import org.apache.shardingsphere.mcp.feature.mask.MaskFeatureDefinition;
@@ -36,6 +40,8 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBu
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationException;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
@@ -43,6 +49,7 @@ import org.mockito.internal.configuration.plugins.Plugins;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -50,6 +57,7 @@ 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.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -57,12 +65,33 @@ import static org.mockito.Mockito.when;
class MaskWorkflowValidationServiceTest {
+ private MockedStatic<TypedSPILoader> typedSPILoader;
+
+ private MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader;
+
+ @BeforeEach
+ void setUp() {
+ typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class);
+ DatabaseType databaseType = mock(DatabaseType.class);
+ typedSPILoader.when(() -> TypedSPILoader.findService(DatabaseType.class, "FixtureDB")).thenReturn(Optional.of(databaseType));
+ DialectDatabaseMetaData dialectDatabaseMetaData = mock(DialectDatabaseMetaData.class);
+ when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.KEEP_ORIGIN);
+ databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType)).thenReturn(Optional.of(dialectDatabaseMetaData));
+ }
+
+ @AfterEach
+ void tearDown() {
+ databaseTypedSPILoader.close();
+ typedSPILoader.close();
+ }
+
@Test
void assertValidateRejectsDifferentSession() {
WorkflowSessionContext workflowSessionContext = new TestWorkflowSessionContext();
workflowSessionContext.save(createSnapshot("plan-1", "session-1", "executed", "create"));
Map<String, Object> actual = createService(mock(MaskRuleInspectionService.class))
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-2",
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-2",
workflowSessionContext.getRequired("plan-1"));
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("issues")).getFirst()).get("code"), is(WorkflowIssueCode.SESSION_OWNERSHIP_MISMATCH));
@@ -76,8 +105,7 @@ class MaskWorkflowValidationServiceTest {
workflowSessionContext.save(snapshot);
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of(Map.of("column", "phone", "algorithm_type", "MASK_FROM_X_TO_Y")));
- MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
- when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
+ MCPFeatureQueryFacade queryFacade = createQueryFacade();
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
Map<String, Object> actual = createService(ruleInspectionService)
@@ -100,14 +128,12 @@ class MaskWorkflowValidationServiceTest {
request.getPrimaryAlgorithmProperties().put("replace-char", "raw-secret");
snapshot.setRequest(request);
String sql = "CREATE MASK RULE `orders` (COLUMNS((NAME=`phone`, TYPE(NAME='mask_from_x_to_y', PROPERTIES('replace-char'='******')))))";
- try (MockedStatic<TypedSPILoader> mockedStatic = mockStatic(TypedSPILoader.class)) {
- mockedStatic.when(() -> TypedSPILoader.checkService(MaskAlgorithm.class, "MASK_FROM_X_TO_Y",
- WorkflowSQLUtils.createProperties(request.getPrimaryAlgorithmProperties()))).thenThrow(new IllegalArgumentException("raw-secret"));
- List<Map<String, Object>> actual = new MaskWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact(sql)));
- assertThat(actual.size(), is(1));
- assertThat(actual.getFirst().get("code"), is(WorkflowIssueCode.SQL_EXECUTABILITY_FAILED));
- assertFalse(String.valueOf(actual).contains("raw-secret"));
- }
+ typedSPILoader.when(() -> TypedSPILoader.checkService(MaskAlgorithm.class, "MASK_FROM_X_TO_Y",
+ WorkflowSQLUtils.createProperties(request.getPrimaryAlgorithmProperties()))).thenThrow(new IllegalArgumentException("raw-secret"));
+ List<Map<String, Object>> actual = new MaskWorkflowValidationService().validate(snapshot, List.of(createRuleDistSQLArtifact(sql)));
+ assertThat(actual.size(), is(1));
+ assertThat(actual.getFirst().get("code"), is(WorkflowIssueCode.SQL_EXECUTABILITY_FAILED));
+ assertFalse(String.valueOf(actual).contains("raw-secret"));
}
@Test
@@ -127,7 +153,7 @@ class MaskWorkflowValidationServiceTest {
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of(Map.of("column", "phone", "algorithm_type", "MASK_FROM_X_TO_Y")));
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
- createService(ruleInspectionService).synchronize(snapshot, metadataQueryFacade, mock(MCPFeatureQueryFacade.class), executionFacade, "session-1");
+ createService(ruleInspectionService).synchronize(snapshot, metadataQueryFacade, createQueryFacade(), executionFacade, "session-1");
verifyNoInteractions(metadataQueryFacade);
verifyNoInteractions(executionFacade);
}
@@ -139,7 +165,7 @@ class MaskWorkflowValidationServiceTest {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of());
WorkflowSynchronizationException actual = assertThrows(WorkflowSynchronizationException.class,
- () -> createService(ruleInspectionService).synchronize(snapshot, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
+ () -> createService(ruleInspectionService).synchronize(snapshot, mock(MCPMetadataQueryFacade.class), createQueryFacade(),
mock(MCPFeatureExecutionFacade.class), "session-1"));
assertThat(actual.getIssueCode(), is(WorkflowIssueCode.RULE_STATE_MISMATCH));
}
@@ -152,7 +178,7 @@ class MaskWorkflowValidationServiceTest {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of());
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("validated"));
assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("passed"));
}
@@ -166,7 +192,7 @@ class MaskWorkflowValidationServiceTest {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of(Map.of("column", "phone", "algorithm_type", "MD5")));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("failed"));
}
@@ -186,7 +212,7 @@ class MaskWorkflowValidationServiceTest {
"algorithm_type", "MASK_FROM_X_TO_Y",
"algorithm_props", Map.of("from-x", "2"))));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("expected"), is("algorithm_props={from-x=1}"));
}
@@ -208,7 +234,7 @@ class MaskWorkflowValidationServiceTest {
"algorithm_type", "MASK_FROM_X_TO_Y",
"algorithm_props", Map.of("replace-char", "raw-actual-secret"))));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("validated"));
assertThat(actual.get("overall_status"), is("passed"));
assertThat(((List<?>) actual.get("mismatches")).size(), is(0));
@@ -233,7 +259,7 @@ class MaskWorkflowValidationServiceTest {
"algorithm_type", "MASK_FROM_X_TO_Y",
"algorithm_props", Map.of("replace-char", "secret_reference:primary.replace-char"))));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("expected"), is("algorithm_props={replace-char=******}"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("actual"), is("algorithm_props={replace-char=******}"));
@@ -251,11 +277,17 @@ class MaskWorkflowValidationServiceTest {
MaskRuleInspectionService ruleInspectionService = mock(MaskRuleInspectionService.class);
when(ruleInspectionService.queryMaskRules(any(), any(), any())).thenReturn(List.of(Map.of("column", "phone", "algorithm_type", "MD5")));
Map<String, Object> actual = createService(ruleInspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("mismatches")).getFirst()).get("expected"), is("column=email"));
}
+ private MCPFeatureQueryFacade createQueryFacade() {
+ MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
+ when(result.getDatabaseType(any())).thenReturn("FixtureDB");
+ return result;
+ }
+
private MaskWorkflowValidationService createService(final MaskRuleInspectionService ruleInspectionService) {
MaskWorkflowValidationService result = new MaskWorkflowValidationService();
try {
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowPlanningServiceTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowPlanningServiceTest.java
index 264dc7e30e6..fe4f190af86 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowPlanningServiceTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowPlanningServiceTest.java
@@ -24,7 +24,11 @@ import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
import java.util.List;
import java.util.Map;
@@ -34,12 +38,29 @@ import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
class ReadwriteSplittingWorkflowPlanningServiceTest {
+ private MockedStatic<WorkflowSQLUtils> workflowSQLUtils;
+
+ @BeforeEach
+ void setUp() {
+ workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS);
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier(anyString(), anyString(), anyString()))
+ .thenAnswer(invocation -> invocation.getArgument(1, String.class).equals(invocation.getArgument(2, String.class)));
+ }
+
+ @AfterEach
+ void tearDown() {
+ workflowSQLUtils.close();
+ }
+
@Test
void assertPlanCreateRule() {
WorkflowContextSnapshot actual = createRuleService().plan(new TestWorkflowSessionContext(), mockRuleQueryFacade(List.of()), "session-1", createRuleRequest("create"));
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServiceTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServiceTest.java
index f4bcc1b1322..e606325c1a1 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServiceTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingWorkflowValidationServiceTest.java
@@ -35,6 +35,8 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchroniz
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSynchronizationSupport;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowArtifactBundle.ExecutableWorkflowArtifact;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
@@ -47,6 +49,8 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -54,12 +58,26 @@ import static org.mockito.Mockito.when;
class ReadwriteSplittingWorkflowValidationServiceTest {
+ private MockedStatic<WorkflowSQLUtils> workflowSQLUtils;
+
+ @BeforeEach
+ void setUp() {
+ workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS);
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier(anyString(), anyString(), anyString()))
+ .thenAnswer(invocation -> invocation.getArgument(1, String.class).equals(invocation.getArgument(2, String.class)));
+ }
+
+ @AfterEach
+ void tearDown() {
+ workflowSQLUtils.close();
+ }
+
@Test
void assertValidateRejectsDifferentSession() {
WorkflowSessionContext workflowSessionContext = new TestWorkflowSessionContext();
workflowSessionContext.save(createRuleSnapshot("plan-1", "session-1", "executed", "create"));
Map<String, Object> actual = createRuleService(mock(ReadwriteSplittingInspectionService.class))
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-2",
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-2",
workflowSessionContext.getRequired("plan-1"));
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) ((List<?>) actual.get("issues")).get(0)).get("code"), is(WorkflowIssueCode.SESSION_OWNERSHIP_MISMATCH));
@@ -72,8 +90,7 @@ class ReadwriteSplittingWorkflowValidationServiceTest {
workflowSessionContext.save(snapshot);
ReadwriteSplittingInspectionService inspectionService = mock(ReadwriteSplittingInspectionService.class);
when(inspectionService.queryRules(any(), any())).thenReturn(List.of(createRuleRow()));
- MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
- when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
+ MCPFeatureQueryFacade queryFacade = createQueryFacade();
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
Map<String, Object> actual = createRuleService(inspectionService).validate(workflowSessionContext, metadataQueryFacade, queryFacade, executionFacade, "session-1", snapshot);
@@ -119,7 +136,7 @@ class ReadwriteSplittingWorkflowValidationServiceTest {
ReadwriteSplittingInspectionService inspectionService = mock(ReadwriteSplittingInspectionService.class);
when(inspectionService.queryRules(any(), any())).thenReturn(List.of());
Map<String, Object> actual = createRuleService(inspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("failed"));
assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("failed"));
}
@@ -132,7 +149,7 @@ class ReadwriteSplittingWorkflowValidationServiceTest {
ReadwriteSplittingInspectionService inspectionService = mock(ReadwriteSplittingInspectionService.class);
when(inspectionService.queryRules(any(), any())).thenReturn(List.of());
Map<String, Object> actual = createRuleService(inspectionService)
- .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
+ .validate(workflowSessionContext, mock(MCPMetadataQueryFacade.class), createQueryFacade(), mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("validated"));
}
@@ -143,8 +160,7 @@ class ReadwriteSplittingWorkflowValidationServiceTest {
workflowSessionContext.save(snapshot);
ReadwriteSplittingInspectionService inspectionService = mock(ReadwriteSplittingInspectionService.class);
when(inspectionService.queryRuleStatus(any(), any(), any())).thenReturn(List.of(createStatusRow("ENABLED")));
- MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
- when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
+ MCPFeatureQueryFacade queryFacade = createQueryFacade();
Map<String, Object> actual = createStatusService(inspectionService).validate(
workflowSessionContext, mock(MCPMetadataQueryFacade.class), queryFacade, mock(MCPFeatureExecutionFacade.class), "session-1", snapshot);
assertThat(actual.get("status"), is("validated"));
@@ -157,11 +173,17 @@ class ReadwriteSplittingWorkflowValidationServiceTest {
ReadwriteSplittingInspectionService inspectionService = mock(ReadwriteSplittingInspectionService.class);
when(inspectionService.queryRuleStatus(any(), any(), any())).thenReturn(List.of(createStatusRow("DISABLED")));
WorkflowSynchronizationException actual = assertThrows(WorkflowSynchronizationException.class,
- () -> createStatusService(inspectionService).synchronize(snapshot, mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class),
+ () -> createStatusService(inspectionService).synchronize(snapshot, mock(MCPMetadataQueryFacade.class), createQueryFacade(),
mock(MCPFeatureExecutionFacade.class), "session-1"));
assertThat(actual.getIssueCode(), is(WorkflowIssueCode.RULE_STATE_MISMATCH));
}
+ private MCPFeatureQueryFacade createQueryFacade() {
+ MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
+ when(result.getDatabaseType(any())).thenReturn("FixtureDB");
+ return result;
+ }
+
private ReadwriteSplittingRuleWorkflowValidationService createRuleService(final ReadwriteSplittingInspectionService inspectionService) {
ReadwriteSplittingRuleWorkflowValidationService result = new ReadwriteSplittingRuleWorkflowValidationService();
try {
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningServiceTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningServiceTest.java
index 252d669e9dc..11d8e4bbe60 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningServiceTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningServiceTest.java
@@ -25,7 +25,9 @@ import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
import java.util.List;
import java.util.Map;
@@ -33,7 +35,9 @@ import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
class ShadowWorkflowPlanningServiceTest {
@@ -95,26 +99,34 @@ class ShadowWorkflowPlanningServiceTest {
void assertPlanAlgorithmCleanup() {
ShadowInspectionService inspectionService = mock(ShadowInspectionService.class);
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
ShadowAlgorithmCleanupWorkflowRequest request = createCleanupRequest();
when(inspectionService.queryAlgorithms(queryFacade, "logic_db")).thenReturn(List.of(Map.of("shadow_algorithm_name", "unused_algorithm")));
when(inspectionService.queryTableRules(queryFacade, "logic_db")).thenReturn(List.of());
when(inspectionService.queryDefaultAlgorithm(queryFacade, "logic_db")).thenReturn(List.of());
- WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService(inspectionService, new ShadowDistSQLPlanningService())
- .planAlgorithmCleanup(new TestWorkflowSessionContext(), queryFacade, "session-1", request);
- assertThat(actual.getRuleArtifacts().getFirst().getSql(), is("DROP SHADOW ALGORITHM `unused_algorithm`"));
+ try (MockedStatic<WorkflowSQLUtils> workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS)) {
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "unused_algorithm", "unused_algorithm")).thenReturn(true);
+ WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService(inspectionService, new ShadowDistSQLPlanningService())
+ .planAlgorithmCleanup(new TestWorkflowSessionContext(), queryFacade, "session-1", request);
+ assertThat(actual.getRuleArtifacts().getFirst().getSql(), is("DROP SHADOW ALGORITHM `unused_algorithm`"));
+ }
}
@Test
void assertPlanAlgorithmCleanupReferenced() {
ShadowInspectionService inspectionService = mock(ShadowInspectionService.class);
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
ShadowAlgorithmCleanupWorkflowRequest request = createCleanupRequest();
when(inspectionService.queryAlgorithms(queryFacade, "logic_db")).thenReturn(List.of(Map.of("shadow_algorithm_name", "unused_algorithm")));
when(inspectionService.queryTableRules(queryFacade, "logic_db")).thenReturn(List.of(Map.of("shadow_algorithm_name", "unused_algorithm")));
when(inspectionService.queryDefaultAlgorithm(queryFacade, "logic_db")).thenReturn(List.of());
- WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService(inspectionService, new ShadowDistSQLPlanningService())
- .planAlgorithmCleanup(new TestWorkflowSessionContext(), queryFacade, "session-1", request);
- assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_FAILED));
+ try (MockedStatic<WorkflowSQLUtils> workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS)) {
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "unused_algorithm", "unused_algorithm")).thenReturn(true);
+ WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService(inspectionService, new ShadowDistSQLPlanningService())
+ .planAlgorithmCleanup(new TestWorkflowSessionContext(), queryFacade, "session-1", request);
+ assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_FAILED));
+ }
}
private ShadowRuleWorkflowRequest createRuleRequest() {
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java
index 9866d39fa7d..294e48a7268 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationServiceTest.java
@@ -44,6 +44,7 @@ import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
@@ -54,25 +55,36 @@ class ShadowWorkflowValidationServiceTest {
void assertValidateRule() {
ShadowInspectionService inspectionService = mock(ShadowInspectionService.class);
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
when(inspectionService.queryRules(queryFacade, "logic_db")).thenReturn(List.of(Map.of(
"rule_name", "shadow_rule",
"source_name", "demo_ds",
"shadow_name", "demo_ds_shadow",
"shadow_table", "t_order",
"algorithm_type", "VALUE_MATCH")));
- Map<String, Object> actual = createService(inspectionService)
- .validate(new TestWorkflowSessionContext(), mock(MCPMetadataQueryFacade.class), queryFacade, mock(MCPFeatureExecutionFacade.class), "session-1", createRuleSnapshot());
- assertThat(actual.get("overall_status"), is(WorkflowLifecycle.STATUS_PASSED));
+ try (MockedStatic<WorkflowSQLUtils> workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS)) {
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "shadow_rule", "shadow_rule")).thenReturn(true);
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "demo_ds", "demo_ds")).thenReturn(true);
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "demo_ds_shadow", "demo_ds_shadow")).thenReturn(true);
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "t_order", "t_order")).thenReturn(true);
+ Map<String, Object> actual = createService(inspectionService)
+ .validate(new TestWorkflowSessionContext(), mock(MCPMetadataQueryFacade.class), queryFacade, mock(MCPFeatureExecutionFacade.class), "session-1", createRuleSnapshot());
+ assertThat(actual.get("overall_status"), is(WorkflowLifecycle.STATUS_PASSED));
+ }
}
@Test
void assertValidateCleanupFailure() {
ShadowInspectionService inspectionService = mock(ShadowInspectionService.class);
MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ when(queryFacade.getDatabaseType("logic_db")).thenReturn("FixtureDB");
when(inspectionService.queryAlgorithms(queryFacade, "logic_db")).thenReturn(List.of(Map.of("shadow_algorithm_name", "unused_algorithm")));
- Map<String, Object> actual = createService(inspectionService)
- .validate(new TestWorkflowSessionContext(), mock(MCPMetadataQueryFacade.class), queryFacade, mock(MCPFeatureExecutionFacade.class), "session-1", createCleanupSnapshot());
- assertThat(actual.get("status"), is(WorkflowLifecycle.STATUS_FAILED));
+ try (MockedStatic<WorkflowSQLUtils> workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS)) {
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "unused_algorithm", "unused_algorithm")).thenReturn(true);
+ Map<String, Object> actual = createService(inspectionService)
+ .validate(new TestWorkflowSessionContext(), mock(MCPMetadataQueryFacade.class), queryFacade, mock(MCPFeatureExecutionFacade.class), "session-1", createCleanupSnapshot());
+ assertThat(actual.get("status"), is(WorkflowLifecycle.STATUS_FAILED));
+ }
}
@Test
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java
index df33384be26..25033928914 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationServiceTest.java
@@ -49,6 +49,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -77,9 +78,12 @@ class ShardingWorkflowValidationServiceTest {
MCPFeatureQueryFacade queryFacade = createQueryFacade();
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
- Map<String, Object> actual = createService(inspectionService).validate(workflowSessionContext, metadataQueryFacade, queryFacade, executionFacade, "session-1", snapshot);
- assertThat(actual.get("status"), is("validated"));
- assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("passed"));
+ try (MockedStatic<WorkflowSQLUtils> workflowSQLUtils = mockStatic(WorkflowSQLUtils.class, CALLS_REAL_METHODS)) {
+ workflowSQLUtils.when(() -> WorkflowSQLUtils.isSameIdentifier("FixtureDB", "t_order", "t_order")).thenReturn(true);
+ Map<String, Object> actual = createService(inspectionService).validate(workflowSessionContext, metadataQueryFacade, queryFacade, executionFacade, "session-1", snapshot);
+ assertThat(actual.get("status"), is("validated"));
+ assertThat(((Map<?, ?>) actual.get("rule_validation")).get("status"), is("passed"));
+ }
verifyNoInteractions(metadataQueryFacade);
verifyNoInteractions(executionFacade);
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
index 94d1bba8100..45f2094c99b 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialect.java
@@ -17,6 +17,8 @@
package org.apache.shardingsphere.mcp.support.database.capability;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
@@ -28,33 +30,24 @@ import org.apache.shardingsphere.database.connector.core.metadata.identifier.Ide
import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.infra.spi.exception.ServiceProviderNotFoundException;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import java.util.Collection;
-import java.util.List;
import java.util.Optional;
/**
* MCP database dialect capabilities.
*/
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class MCPDatabaseDialect {
- private final String databaseType;
+ private final DialectDatabaseMetaData dialectDatabaseMetaData;
- private final Optional<DialectDatabaseMetaData> dialectDatabaseMetaData;
-
- private final Optional<SystemDatabase> systemDatabase;
+ private final SystemDatabase systemDatabase;
private final Optional<MCPDatabaseCapabilityOption> option;
- private MCPDatabaseDialect(final String databaseType, final Optional<DialectDatabaseMetaData> dialectDatabaseMetaData, final Optional<SystemDatabase> systemDatabase,
- final Optional<MCPDatabaseCapabilityOption> option) {
- this.databaseType = databaseType;
- this.dialectDatabaseMetaData = dialectDatabaseMetaData;
- this.systemDatabase = systemDatabase;
- this.option = option;
- }
-
/**
* Create MCP database dialect capabilities.
*
@@ -63,17 +56,12 @@ public final class MCPDatabaseDialect {
*/
public static MCPDatabaseDialect of(final String databaseType) {
String actualDatabaseType = trimToEmpty(databaseType);
- Optional<DatabaseType> databaseTypeFromSPI = actualDatabaseType.isEmpty()
- ? Optional.empty()
- : TypedSPILoader.findService(DatabaseType.class, actualDatabaseType);
- Optional<MCPDatabaseCapabilityOption> option = actualDatabaseType.isEmpty()
- ? Optional.empty()
- : TypedSPILoader.findService(MCPDatabaseCapabilityOption.class, actualDatabaseType);
- return new MCPDatabaseDialect(actualDatabaseType, databaseTypeFromSPI.flatMap(MCPDatabaseDialect::findDialectDatabaseMetaData), databaseTypeFromSPI.map(SystemDatabase::new), option);
- }
-
- private static Optional<DialectDatabaseMetaData> findDialectDatabaseMetaData(final DatabaseType databaseType) {
- return DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType);
+ DatabaseType databaseTypeFromSPI = TypedSPILoader.findService(DatabaseType.class, actualDatabaseType)
+ .orElseThrow(() -> new ServiceProviderNotFoundException(DatabaseType.class, actualDatabaseType));
+ DialectDatabaseMetaData dialectDatabaseMetaData = DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseTypeFromSPI)
+ .orElseThrow(() -> new ServiceProviderNotFoundException(DialectDatabaseMetaData.class, actualDatabaseType));
+ return new MCPDatabaseDialect(dialectDatabaseMetaData, new SystemDatabase(databaseTypeFromSPI),
+ TypedSPILoader.findService(MCPDatabaseCapabilityOption.class, actualDatabaseType));
}
/**
@@ -82,11 +70,7 @@ public final class MCPDatabaseDialect {
* @return identifier quote character
*/
public QuoteCharacter getIdentifierQuoteCharacter() {
- return dialectDatabaseMetaData.map(DialectDatabaseMetaData::getQuoteCharacter).orElseGet(this::getFallbackIdentifierQuoteCharacter);
- }
-
- private QuoteCharacter getFallbackIdentifierQuoteCharacter() {
- return databaseType.isEmpty() ? QuoteCharacter.BACK_QUOTE : QuoteCharacter.QUOTE;
+ return dialectDatabaseMetaData.getQuoteCharacter();
}
/**
@@ -100,14 +84,10 @@ public final class MCPDatabaseDialect {
}
private IdentifierCasePolicySet getIdentifierCasePolicySet() {
- return findDialectIdentifierCasePolicySet()
+ return createDialectIdentifierCasePolicySet(dialectDatabaseMetaData)
.orElseGet(() -> option.map(MCPDatabaseCapabilityOption::getIdentifierCasePolicySet).orElseGet(IdentifierCasePolicyFactory::newSensitivePolicySet));
}
- private Optional<IdentifierCasePolicySet> findDialectIdentifierCasePolicySet() {
- return dialectDatabaseMetaData.flatMap(MCPDatabaseDialect::createDialectIdentifierCasePolicySet);
- }
-
private static Optional<IdentifierCasePolicySet> createDialectIdentifierCasePolicySet(final DialectDatabaseMetaData dialectDatabaseMetaData) {
IdentifierPatternType identifierPatternType = dialectDatabaseMetaData.getIdentifierPatternType();
return IdentifierPatternType.KEEP_ORIGIN != identifierPatternType
@@ -121,7 +101,7 @@ public final class MCPDatabaseDialect {
* @return default schema semantics
*/
public DialectSchemaSemantics getDefaultSchemaSemantics() {
- return dialectDatabaseMetaData.map(each -> each.getSchemaOption().getSchemaSemantics()).orElse(DialectSchemaSemantics.NATIVE_SCHEMA);
+ return dialectDatabaseMetaData.getSchemaOption().getSchemaSemantics();
}
/**
@@ -144,11 +124,7 @@ public final class MCPDatabaseDialect {
* @return whether unquoted identifiers are folded
*/
public boolean isUnquotedIdentifierCaseFolded() {
- return isFoldedDialectIdentifierPattern();
- }
-
- private boolean isFoldedDialectIdentifierPattern() {
- return dialectDatabaseMetaData.map(each -> IdentifierPatternType.KEEP_ORIGIN != each.getIdentifierPatternType()).orElse(false);
+ return IdentifierPatternType.KEEP_ORIGIN != dialectDatabaseMetaData.getIdentifierPatternType();
}
/**
@@ -157,7 +133,7 @@ public final class MCPDatabaseDialect {
* @return sequence metadata query
*/
public boolean isSequenceSupported() {
- return dialectDatabaseMetaData.flatMap(DialectDatabaseMetaData::getSequenceOption).isPresent();
+ return dialectDatabaseMetaData.getSequenceOption().isPresent();
}
/**
@@ -171,7 +147,7 @@ public final class MCPDatabaseDialect {
if (actualSchemaName.isEmpty()) {
return false;
}
- return containsSystemSchema(getSystemSchemas(), actualSchemaName);
+ return containsSystemSchema(systemDatabase.getSystemSchemas(), actualSchemaName);
}
/**
@@ -186,10 +162,6 @@ public final class MCPDatabaseDialect {
return isSystemSchema(schemaName) || DialectSchemaSemantics.DATABASE_AS_SCHEMA == defaultSchemaSemantics && isSystemSchema(catalogName);
}
- private Collection<String> getSystemSchemas() {
- return systemDatabase.map(SystemDatabase::getSystemSchemas).filter(each -> !each.isEmpty()).orElseGet(List::of);
- }
-
private static boolean containsSystemSchema(final Collection<String> systemSchemas, final String schemaName) {
for (String each : systemSchemas) {
if (schemaName.equalsIgnoreCase(each)) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/spi/MCPFeatureQueryFacade.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/spi/MCPFeatureQueryFacade.java
index 5b688c5f64e..6c26a85af4c 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/spi/MCPFeatureQueryFacade.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/spi/MCPFeatureQueryFacade.java
@@ -49,9 +49,7 @@ public interface MCPFeatureQueryFacade {
* @param databaseName database name
* @return database type
*/
- default String getDatabaseType(final String databaseName) {
- return "";
- }
+ String getDatabaseType(String databaseName);
/**
* Query column definition.
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java
index 8314c26a3ce..220d4c73782 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidator.java
@@ -20,7 +20,8 @@ package org.apache.shardingsphere.mcp.support.workflow.service;
import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
-import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
+import org.apache.shardingsphere.mcp.support.database.exception.DatabaseCapabilityNotFoundException;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -34,7 +35,6 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
/**
* Validator for workflow planning context.
@@ -71,12 +71,14 @@ public final class WorkflowPlanningContextValidator {
* Ensure workflow planning context is complete and valid.
*
* @param metadataQueryFacade metadata query facade
+ * @param queryFacade query facade for database capability resolution
* @param request workflow request
* @param clarifiedIntent clarified intent
* @param snapshot workflow snapshot
* @return whether planning context is ready
+ * @throws DatabaseCapabilityNotFoundException when database profile or capability does not exist
*/
- public boolean ensurePlanningContext(final MCPMetadataQueryFacade metadataQueryFacade, final WorkflowRequest request,
+ public boolean ensurePlanningContext(final MCPMetadataQueryFacade metadataQueryFacade, final MCPFeatureQueryFacade queryFacade, final WorkflowRequest request,
final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot) {
if (isEmptyIdentifier(request.getDatabase())) {
clarifiedIntent.getClarificationMessages().add("Please provide logical database first.");
@@ -91,8 +93,9 @@ public final class WorkflowPlanningContextValidator {
snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
return false;
}
- Optional<RuntimeDatabaseProfile> databaseProfile = metadataQueryFacade.queryDatabase(WorkflowSQLUtils.normalizeIdentifier(request.getDatabase()));
- String databaseType = databaseProfile.map(RuntimeDatabaseProfile::getDatabaseType).orElse("");
+ String databaseName = WorkflowSQLUtils.normalizeIdentifier(request.getDatabase());
+ metadataQueryFacade.queryDatabase(databaseName).orElseThrow(DatabaseCapabilityNotFoundException::new);
+ String databaseType = queryFacade.getDatabaseType(databaseName);
request.setSchema(resolveSchema(metadataQueryFacade, request, clarifiedIntent, databaseType));
if (!ensureSupportedIdentifiers(WorkflowFieldNames.SCHEMA, List.of(request.getSchema()), snapshot, "discovering")) {
snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
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 b3eabb9d4c4..7f4c8168861 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
@@ -17,6 +17,8 @@
package org.apache.shardingsphere.mcp.support.workflow.service;
+import org.apache.shardingsphere.mcp.support.database.exception.DatabaseCapabilityNotFoundException;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
@@ -219,13 +221,15 @@ public final class WorkflowPlanningSupport {
* Ensure workflow planning context is complete and valid.
*
* @param metadataQueryFacade metadata query facade
+ * @param queryFacade query facade for database capability resolution
* @param request workflow request
* @param clarifiedIntent clarified intent
* @param snapshot workflow snapshot
* @return whether planning context is ready
+ * @throws DatabaseCapabilityNotFoundException when database profile or capability does not exist
*/
- public boolean ensurePlanningContext(final MCPMetadataQueryFacade metadataQueryFacade, final WorkflowRequest request,
+ public boolean ensurePlanningContext(final MCPMetadataQueryFacade metadataQueryFacade, final MCPFeatureQueryFacade queryFacade, final WorkflowRequest request,
final ClarifiedIntent clarifiedIntent, final WorkflowContextSnapshot snapshot) {
- return contextValidator.ensurePlanningContext(metadataQueryFacade, request, clarifiedIntent, snapshot);
+ return contextValidator.ensurePlanningContext(metadataQueryFacade, queryFacade, request, clarifiedIntent, snapshot);
}
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java
index c8262a9dd1a..e54ffbf70b4 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseCapabilityProviderTest.java
@@ -88,6 +88,14 @@ class MCPDatabaseCapabilityProviderTest {
assertThat(actual.get().getSchemaExecutionSemantics(), is(SchemaExecutionSemantics.FIXED_TO_DATABASE));
}
+ @Test
+ void assertProvideWithoutCapabilityOption() {
+ MCPDatabaseCapabilityProvider provider = createCapabilityProvider("FixtureDB", "",
+ new CapabilityFixture(false, false, false, DialectSchemaSemantics.NATIVE_SCHEMA));
+ assertThat(provider.findDatabaseProfile("logic_db").orElseThrow().getDatabaseType(), is("FixtureDB"));
+ assertFalse(provider.provide("logic_db").isPresent());
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("provideCapabilityMatrixArguments")
void assertProvideWithCapabilityMatrix(final String name, final String databaseType, final boolean expectedTransactionControl,
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
index 6fdf31fae86..bc1daf770bb 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/database/capability/MCPDatabaseDialectTest.java
@@ -29,6 +29,7 @@ import org.apache.shardingsphere.database.connector.core.metadata.identifier.Ide
import org.apache.shardingsphere.database.connector.core.metadata.identifier.IdentifierScope;
import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.infra.spi.exception.ServiceProviderNotFoundException;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -43,6 +44,7 @@ import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
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.mockStatic;
@@ -51,9 +53,8 @@ import static org.mockito.Mockito.when;
class MCPDatabaseDialectTest {
@Test
- void assertOf() {
- MCPDatabaseDialect actual = MCPDatabaseDialect.of("");
- assertThat(actual.getIdentifierQuoteCharacter(), is(QuoteCharacter.BACK_QUOTE));
+ void assertOfWithEmptyDatabaseType() {
+ assertThrows(ServiceProviderNotFoundException.class, () -> MCPDatabaseDialect.of(""));
}
@ParameterizedTest(name = "{0}")
@@ -71,15 +72,23 @@ class MCPDatabaseDialectTest {
}
@Test
- void assertGetIdentifierQuoteCharacterWithUnknownDatabaseType() {
- QuoteCharacter actual = MCPDatabaseDialect.of("FixtureDB").getIdentifierQuoteCharacter();
- assertThat(actual, is(QuoteCharacter.QUOTE));
+ void assertOfWithUnknownDatabaseType() {
+ assertThrows(ServiceProviderNotFoundException.class, () -> MCPDatabaseDialect.of("FixtureDB"));
}
@Test
void assertGetIdentifierCasePolicyWithCaseInsensitiveIdentifier() {
- IdentifierCasePolicy actual = MCPDatabaseDialect.of("MySQL").getIdentifierCasePolicy(IdentifierScope.TABLE);
- assertTrue(actual.matches("phone", "Phone", QuoteCharacter.QUOTE));
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ DatabaseType databaseType = mockDatabaseType("FixtureWithOption", typedSPILoader);
+ DialectDatabaseMetaData dialectDatabaseMetaData = mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
+ when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.KEEP_ORIGIN);
+ MCPDatabaseCapabilityOption option = mockMCPDatabaseCapabilityOption("FixtureWithOption", typedSPILoader);
+ when(option.getIdentifierCasePolicySet()).thenReturn(IdentifierCasePolicyFactory.newMySQLInsensitivePolicySet());
+ IdentifierCasePolicy actual = MCPDatabaseDialect.of("FixtureWithOption").getIdentifierCasePolicy(IdentifierScope.TABLE);
+ assertTrue(actual.matches("phone", "Phone", QuoteCharacter.QUOTE));
+ }
}
@Test
@@ -125,14 +134,26 @@ class MCPDatabaseDialectTest {
}
@Test
- void assertGetDefaultSchemaSemanticsWithUnknownDatabaseType() {
- assertThat(MCPDatabaseDialect.of("FixtureDB").getDefaultSchemaSemantics(), is(DialectSchemaSemantics.NATIVE_SCHEMA));
+ void assertOfWithoutDialectDatabaseMetaData() {
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ DatabaseType databaseType = mockDatabaseType("Fixture", typedSPILoader);
+ databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType)).thenReturn(Optional.empty());
+ assertThrows(ServiceProviderNotFoundException.class, () -> MCPDatabaseDialect.of("Fixture"));
+ }
}
@ParameterizedTest(name = "{0}")
@MethodSource("getTransactionCapabilityArguments")
void assertGetTransactionCapability(final String name, final boolean transactionSupported, final boolean savepointSupported, final TransactionCapability expected) {
- assertThat(MCPDatabaseDialect.of("Fixture").getTransactionCapability(transactionSupported, savepointSupported), is(expected));
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ DatabaseType databaseType = mockDatabaseType("Fixture", typedSPILoader);
+ mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
+ assertThat(MCPDatabaseDialect.of("Fixture").getTransactionCapability(transactionSupported, savepointSupported), is(expected));
+ }
}
@ParameterizedTest(name = "{0}")
@@ -149,12 +170,6 @@ class MCPDatabaseDialectTest {
}
}
- @Test
- void assertIsUnquotedIdentifierCaseFoldedWithUnknownDatabaseType() {
- boolean actual = MCPDatabaseDialect.of("FixtureDB").isUnquotedIdentifierCaseFolded();
- assertFalse(actual);
- }
-
@Test
void assertIsSequenceSupported() {
try (
@@ -185,7 +200,7 @@ class MCPDatabaseDialectTest {
MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
DatabaseType databaseType = mockDatabaseType("Fixture", typedSPILoader);
- mockDialectDatabaseMetaDataAbsent(databaseType, databaseTypedSPILoader);
+ mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
DialectSystemDatabase dialectSystemDatabase = mockDialectSystemDatabase(databaseType, databaseTypedSPILoader);
when(dialectSystemDatabase.getSystemSchemas()).thenReturn(List.of("fixture_system"));
boolean actual = MCPDatabaseDialect.of("Fixture").isSystemSchema("fixture_system");
@@ -199,7 +214,7 @@ class MCPDatabaseDialectTest {
MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
DatabaseType databaseType = mockDatabaseType("Fixture", typedSPILoader);
- mockDialectDatabaseMetaDataAbsent(databaseType, databaseTypedSPILoader);
+ mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
DialectSystemDatabase dialectSystemDatabase = mockDialectSystemDatabase(databaseType, databaseTypedSPILoader);
when(dialectSystemDatabase.getSystemSchemas()).thenReturn(List.of("fixture_system"));
boolean actual = MCPDatabaseDialect.of("Fixture").isSystemSchema("", "fixture_system", DialectSchemaSemantics.DATABASE_AS_SCHEMA);
@@ -208,21 +223,28 @@ class MCPDatabaseDialectTest {
}
@Test
- void assertIsSystemSchemaWithUserSchema() {
- boolean actual = MCPDatabaseDialect.of("MySQL").isSystemSchema("orders");
- assertFalse(actual);
- }
-
- @Test
- void assertIsSystemSchemaIgnoresUnknownDatabaseType() {
- boolean actual = MCPDatabaseDialect.of("FixtureDB").isSystemSchema("INFORMATION_SCHEMA");
- assertFalse(actual);
+ void assertIsSystemSchemaWithoutDialectSystemDatabase() {
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ DatabaseType databaseType = mockDatabaseType("Fixture", typedSPILoader);
+ mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
+ databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectSystemDatabase.class, databaseType)).thenReturn(Optional.empty());
+ assertFalse(MCPDatabaseDialect.of("Fixture").isSystemSchema("information_schema"));
+ }
}
@Test
- void assertIsSystemSchemaIgnoresOtherDatabaseType() {
- boolean actual = MCPDatabaseDialect.of("SQLServer").isSystemSchema("pg_catalog");
- assertFalse(actual);
+ void assertIsSystemSchemaWithOtherSchema() {
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ DatabaseType databaseType = mockDatabaseType("Fixture", typedSPILoader);
+ mockDialectDatabaseMetaData(databaseType, databaseTypedSPILoader);
+ DialectSystemDatabase dialectSystemDatabase = mockDialectSystemDatabase(databaseType, databaseTypedSPILoader);
+ when(dialectSystemDatabase.getSystemSchemas()).thenReturn(List.of("fixture_system"));
+ assertFalse(MCPDatabaseDialect.of("Fixture").isSystemSchema("pg_catalog"));
+ }
}
private static Stream<Arguments> getIdentifierQuoteCharacterArguments() {
@@ -252,10 +274,6 @@ class MCPDatabaseDialectTest {
return result;
}
- private static void mockDialectDatabaseMetaDataAbsent(final DatabaseType databaseType, final MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader) {
- databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType)).thenReturn(Optional.empty());
- }
-
private static MCPDatabaseCapabilityOption mockMCPDatabaseCapabilityOption(final String databaseType, final MockedStatic<TypedSPILoader> typedSPILoader) {
MCPDatabaseCapabilityOption result = mock(MCPDatabaseCapabilityOption.class);
typedSPILoader.when(() -> TypedSPILoader.findService(MCPDatabaseCapabilityOption.class, databaseType)).thenReturn(Optional.of(result));
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java
index 6b909bfa23e..866f152ca74 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningContextValidatorTest.java
@@ -18,17 +18,24 @@
package org.apache.shardingsphere.mcp.support.workflow.service;
import org.apache.shardingsphere.database.connector.core.metadata.database.enums.TableType;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
+import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.mcp.support.database.exception.DatabaseCapabilityNotFoundException;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
import java.util.List;
import java.util.Map;
@@ -37,8 +44,15 @@ import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
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.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
class WorkflowPlanningContextValidatorTest {
@@ -49,11 +63,57 @@ class WorkflowPlanningContextValidatorTest {
void assertEnsurePlanningContextRejectsMissingDatabase() {
ClarifiedIntent clarifiedIntent = new ClarifiedIntent();
WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
- boolean actual = validator.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), new WorkflowRequest(), clarifiedIntent, snapshot);
+ MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ boolean actual = validator.ensurePlanningContext(metadataQueryFacade, queryFacade, new WorkflowRequest(), clarifiedIntent, snapshot);
assertFalse(actual);
assertThat(snapshot.getStatus(), is("clarifying"));
assertThat(clarifiedIntent.getClarificationMessages(), is(List.of("Please provide logical database first.")));
assertThat(snapshot.getIssues().getFirst().getCode(), is(WorkflowIssueCode.DATABASE_REQUIRED));
+ verifyNoInteractions(metadataQueryFacade, queryFacade);
+ }
+
+ @Test
+ void assertEnsurePlanningContextRejectsUnknownDatabase() {
+ WorkflowRequest request = new WorkflowRequest();
+ request.setDatabase("logic_db");
+ request.setTable("orders");
+ request.setColumn("phone");
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ assertThrows(DatabaseCapabilityNotFoundException.class,
+ () -> validator.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), queryFacade, request, new ClarifiedIntent(), new WorkflowContextSnapshot()));
+ verifyNoInteractions(queryFacade);
+ }
+
+ @Test
+ void assertEnsurePlanningContextRejectsUnsupportedIdentifier() {
+ WorkflowRequest request = new WorkflowRequest();
+ request.setDatabase("logic_db");
+ request.setTable("orders\ndrop");
+ request.setColumn("phone");
+ WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+ MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ assertFalse(validator.ensurePlanningContext(metadataQueryFacade, queryFacade, request, new ClarifiedIntent(), snapshot));
+ assertThat(snapshot.getIssues().getFirst().getCode(), is(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER));
+ verifyNoInteractions(metadataQueryFacade, queryFacade);
+ }
+
+ @Test
+ void assertEnsurePlanningContextRejectsDatabaseWithoutCapability() {
+ WorkflowRequest request = new WorkflowRequest();
+ request.setDatabase("logic_db");
+ request.setTable("orders");
+ request.setColumn("phone");
+ MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
+ when(metadataQueryFacade.queryDatabase("logic_db")).thenReturn(Optional.of(createDatabaseMetadata()));
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ when(queryFacade.getDatabaseType("logic_db")).thenThrow(new DatabaseCapabilityNotFoundException());
+ assertThrows(DatabaseCapabilityNotFoundException.class,
+ () -> validator.ensurePlanningContext(metadataQueryFacade, queryFacade, request, new ClarifiedIntent(), new WorkflowContextSnapshot()));
+ verify(metadataQueryFacade, never()).querySchemas(anyString());
+ verify(metadataQueryFacade, never()).queryTable(anyString(), anyString(), anyString());
+ verify(metadataQueryFacade, never()).queryTableColumn(anyString(), anyString(), anyString(), anyString());
}
@Test
@@ -69,11 +129,22 @@ class WorkflowPlanningContextValidatorTest {
when(metadataQueryFacade.querySchemas("logic_db")).thenReturn(List.of(createSchemaMetadata()));
when(metadataQueryFacade.queryTable("logic_db", "public", "orders")).thenReturn(Optional.of(createTableMetadata()));
when(metadataQueryFacade.queryTableColumn("logic_db", "public", "orders", "phone")).thenReturn(Optional.of(createColumnMetadata()));
- boolean actual = validator.ensurePlanningContext(metadataQueryFacade, request, clarifiedIntent, snapshot);
- assertTrue(actual);
- assertThat(request.getSchema(), is("public"));
- assertThat(clarifiedIntent.getInferredValues().get("schema"), is("public"));
- assertTrue(snapshot.getIssues().isEmpty());
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ when(queryFacade.getDatabaseType("logic_db")).thenReturn("Fixture");
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ DatabaseType databaseType = mock(DatabaseType.class);
+ typedSPILoader.when(() -> TypedSPILoader.findService(DatabaseType.class, "Fixture")).thenReturn(Optional.of(databaseType));
+ DialectDatabaseMetaData dialectDatabaseMetaData = mock(DialectDatabaseMetaData.class);
+ when(dialectDatabaseMetaData.getIdentifierPatternType()).thenReturn(IdentifierPatternType.KEEP_ORIGIN);
+ databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseType)).thenReturn(Optional.of(dialectDatabaseMetaData));
+ boolean actual = validator.ensurePlanningContext(metadataQueryFacade, queryFacade, request, clarifiedIntent, snapshot);
+ assertTrue(actual);
+ assertThat(request.getSchema(), is("public"));
+ assertThat(clarifiedIntent.getInferredValues().get("schema"), is("public"));
+ assertTrue(snapshot.getIssues().isEmpty());
+ }
}
@Test
@@ -95,7 +166,7 @@ class WorkflowPlanningContextValidatorTest {
}
private RuntimeDatabaseProfile createDatabaseMetadata() {
- return new RuntimeDatabaseProfile("logic_db", "MySQL", "8.0", true, true);
+ return new RuntimeDatabaseProfile("logic_db", "Fixture", "1.0", true, true);
}
private ShardingSphereSchema createSchemaMetadata() {
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 e8bcab2c904..ab1aec60de4 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
@@ -27,6 +27,7 @@ import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSp
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseProfile;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
@@ -66,7 +67,7 @@ class WorkflowPlanningSupportTest {
ClarifiedIntent clarifiedIntent = new ClarifiedIntent();
WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
WorkflowRequest request = new WorkflowRequest();
- boolean actual = planningSupport.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), request, clarifiedIntent, snapshot);
+ boolean actual = planningSupport.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), request, clarifiedIntent, snapshot);
assertFalse(actual);
assertThat(snapshot.getStatus(), is("clarifying"));
assertThat(clarifiedIntent.getClarificationMessages(), is(List.of("Please provide logical database first.")));
@@ -79,7 +80,7 @@ class WorkflowPlanningSupportTest {
WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
WorkflowRequest request = new WorkflowRequest();
request.setDatabase("``");
- boolean actual = planningSupport.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), request, clarifiedIntent, snapshot);
+ boolean actual = planningSupport.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), request, clarifiedIntent, snapshot);
assertFalse(actual);
assertThat(snapshot.getStatus(), is("clarifying"));
assertThat(clarifiedIntent.getClarificationMessages(), is(List.of("Please provide logical database first.")));
@@ -94,7 +95,7 @@ class WorkflowPlanningSupportTest {
request.setDatabase("logic_db");
request.setTable("orders\ndrop");
request.setColumn("phone");
- boolean actual = planningSupport.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), request, clarifiedIntent, snapshot);
+ boolean actual = planningSupport.ensurePlanningContext(mock(MCPMetadataQueryFacade.class), mock(MCPFeatureQueryFacade.class), request, clarifiedIntent, snapshot);
assertFalse(actual);
assertThat(snapshot.getStatus(), is("failed"));
assertThat(snapshot.getIssues().getFirst().getCode(), is(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER));
@@ -110,9 +111,10 @@ class WorkflowPlanningSupportTest {
ClarifiedIntent clarifiedIntent = new ClarifiedIntent();
WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
MCPMetadataQueryFacade metadataQueryFacade = mock(MCPMetadataQueryFacade.class);
+ when(metadataQueryFacade.queryDatabase("logic_db")).thenReturn(Optional.of(createDatabaseMetadata()));
when(metadataQueryFacade.queryTable("logic_db", "public", "orders-detail")).thenReturn(Optional.of(createTableMetadata("orders-detail", "Phone Number")));
when(metadataQueryFacade.queryTableColumn("logic_db", "public", "orders-detail", "Phone Number")).thenReturn(Optional.of(createColumnMetadata("Phone Number")));
- boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, request, clarifiedIntent, snapshot);
+ boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, createQueryFacade("MySQL"), request, clarifiedIntent, snapshot);
assertTrue(actual);
assertThat(request.getDatabase(), is("`logic_db`"));
assertThat(request.getSchema(), is("`public`"));
@@ -133,12 +135,17 @@ class WorkflowPlanningSupportTest {
when(metadataQueryFacade.querySchemas("logic_db")).thenReturn(List.of(createSchemaMetadata("public", "orders", "phone")));
when(metadataQueryFacade.queryTable("logic_db", "public", "orders")).thenReturn(Optional.of(createTableMetadata("orders", "phone")));
when(metadataQueryFacade.queryTableColumn("logic_db", "public", "orders", "phone")).thenReturn(Optional.of(createColumnMetadata("phone")));
- boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, request, clarifiedIntent, snapshot);
- assertTrue(actual);
- assertThat(request.getSchema(), is("public"));
- assertThat(clarifiedIntent.getInferredValues().get("schema"), is("public"));
- assertTrue(clarifiedIntent.getClarificationMessages().isEmpty());
- assertTrue(snapshot.getIssues().isEmpty());
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ mockIdentifierPatternType("MySQL", IdentifierPatternType.KEEP_ORIGIN, typedSPILoader, databaseTypedSPILoader);
+ boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, createQueryFacade("MySQL"), request, clarifiedIntent, snapshot);
+ assertTrue(actual);
+ assertThat(request.getSchema(), is("public"));
+ assertThat(clarifiedIntent.getInferredValues().get("schema"), is("public"));
+ assertTrue(clarifiedIntent.getClarificationMessages().isEmpty());
+ assertTrue(snapshot.getIssues().isEmpty());
+ }
}
@Test
@@ -158,7 +165,7 @@ class WorkflowPlanningSupportTest {
MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
mockIdentifierPatternType("PostgreSQL", IdentifierPatternType.LOWER_CASE, typedSPILoader, databaseTypedSPILoader);
- boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, request, clarifiedIntent, snapshot);
+ boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, createQueryFacade("PostgreSQL"), request, clarifiedIntent, snapshot);
assertTrue(actual);
assertTrue(snapshot.getIssues().isEmpty());
}
@@ -181,7 +188,7 @@ class WorkflowPlanningSupportTest {
MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
mockIdentifierPatternType("PostgreSQL", IdentifierPatternType.LOWER_CASE, typedSPILoader, databaseTypedSPILoader);
- boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, request, clarifiedIntent, snapshot);
+ boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, createQueryFacade("PostgreSQL"), request, clarifiedIntent, snapshot);
assertTrue(actual);
assertTrue(snapshot.getIssues().isEmpty());
}
@@ -204,7 +211,7 @@ class WorkflowPlanningSupportTest {
MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
mockIdentifierPatternType("PostgreSQL", IdentifierPatternType.LOWER_CASE, typedSPILoader, databaseTypedSPILoader);
- boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, request, clarifiedIntent, snapshot);
+ boolean actual = planningSupport.ensurePlanningContext(metadataQueryFacade, createQueryFacade("PostgreSQL"), request, clarifiedIntent, snapshot);
assertFalse(actual);
assertThat(snapshot.getStatus(), is("clarifying"));
assertThat(clarifiedIntent.getClarificationMessages(), is(List.of("Please specify schema.")));
@@ -361,6 +368,12 @@ class WorkflowPlanningSupportTest {
databaseTypedSPILoader.when(() -> DatabaseTypedSPILoader.findService(DialectDatabaseMetaData.class, databaseTypeFromSPI)).thenReturn(Optional.of(dialectDatabaseMetaData));
}
+ private MCPFeatureQueryFacade createQueryFacade(final String databaseType) {
+ MCPFeatureQueryFacade result = mock(MCPFeatureQueryFacade.class);
+ when(result.getDatabaseType("logic_db")).thenReturn(databaseType);
+ return result;
+ }
+
private RuntimeDatabaseProfile createDatabaseMetadata() {
return createDatabaseMetadata("MySQL");
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java
index 2c1e248334e..2e4cbcd63ae 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowSQLUtilsTest.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.database.connector.core.metadata.database.metad
import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.IdentifierPatternType;
import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.infra.spi.exception.ServiceProviderNotFoundException;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -100,7 +101,12 @@ class WorkflowSQLUtilsTest {
@Test
void assertCanonicalizeIdentifierKeepsMySQLUnquotedIdentifier() {
- assertThat(WorkflowSQLUtils.canonicalizeIdentifier("MySQL", "Phone"), is("Phone"));
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ mockIdentifierPatternType("MySQL", IdentifierPatternType.KEEP_ORIGIN, typedSPILoader, databaseTypedSPILoader);
+ assertThat(WorkflowSQLUtils.canonicalizeIdentifier("MySQL", "Phone"), is("Phone"));
+ }
}
@Test
@@ -211,9 +217,8 @@ class WorkflowSQLUtilsTest {
}
@Test
- void assertFormatSQLIdentifierUsesFallbackQuoteStyle() {
- String actualValue = WorkflowSQLUtils.formatSQLIdentifier("", "order detail");
- assertThat(actualValue, is("`order detail`"));
+ void assertFormatSQLIdentifierRejectsEmptyDatabaseType() {
+ assertThrows(ServiceProviderNotFoundException.class, () -> WorkflowSQLUtils.formatSQLIdentifier("", "order detail"));
}
@Test
@@ -251,7 +256,12 @@ class WorkflowSQLUtilsTest {
@Test
void assertIsSameIdentifierWithCaseInsensitiveDatabase() {
- assertTrue(WorkflowSQLUtils.isSameIdentifier("MySQL", "Phone", "phone"));
+ try (
+ MockedStatic<TypedSPILoader> typedSPILoader = mockStatic(TypedSPILoader.class, CALLS_REAL_METHODS);
+ MockedStatic<DatabaseTypedSPILoader> databaseTypedSPILoader = mockStatic(DatabaseTypedSPILoader.class)) {
+ mockIdentifierPatternType("MySQL", IdentifierPatternType.KEEP_ORIGIN, typedSPILoader, databaseTypedSPILoader);
+ assertTrue(WorkflowSQLUtils.isSameIdentifier("MySQL", "Phone", "phone"));
+ }
}
@Test