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/06/14 08:59:18 UTC
(shardingsphere) branch master updated: Align plugin workflows with structured planning contracts (#38849)
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 f28d59023c3 Align plugin workflows with structured planning contracts (#38849)
f28d59023c3 is described below
commit f28d59023c301d8647d74c034dd2cfe0166daacb
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Sun Jun 14 16:59:11 2026 +0800
Align plugin workflows with structured planning contracts (#38849)
* Align plugin workflows with structured planning contracts
- validate prompt placeholders while preserving resource URI templates
- add plan/apply navigation and rendered continuation context for MCP plugins
- provide algorithm recommendations and property requirements for readwrite, shadow, and sharding workflows
- decompose sharding planning requests, services, and resource handlers with focused tests
- update Speckit tasks and MCP workflow documentation
* Align plugin workflows with structured planning contracts
- validate prompt placeholders while preserving resource URI templates
- add plan/apply navigation and rendered continuation context for MCP plugins
- provide algorithm recommendations and property requirements for readwrite, shadow, and sharding workflows
- decompose sharding planning requests, services, and resource handlers with focused tests
- update Speckit tasks and MCP workflow documentation
---
.../shardingsphere-mcp/features/broadcast.cn.md | 2 +
.../shardingsphere-mcp/features/broadcast.en.md | 2 +
.../features/plugin-workflow.cn.md | 13 +
.../features/plugin-workflow.en.md | 13 +
.../features/readwrite-splitting.cn.md | 3 +
.../features/readwrite-splitting.en.md | 3 +
.../shardingsphere-mcp/features/shadow.cn.md | 3 +
.../shardingsphere-mcp/features/shadow.en.md | 3 +
.../shardingsphere-mcp/features/sharding.cn.md | 3 +
.../shardingsphere-mcp/features/sharding.en.md | 3 +
.../prompts/plan-broadcast-rule.md | 7 +
.../BroadcastToolDescriptorValidatorTest.java | 13 +
.../PlanReadwriteSplittingRuleToolHandler.java | 24 +-
.../ReadwriteSplittingRuleWorkflowRequest.java | 5 +
...eSplittingAlgorithmPropertyTemplateService.java | 53 ++++
...iteSplittingAlgorithmRecommendationService.java | 85 +++++
.../ReadwriteSplittingInspectionService.java | 3 +-
...dwriteSplittingRuleWorkflowPlanningService.java | 30 ++
.../mcp-descriptor-readwrite-splitting.yaml | 35 ++-
.../prompts/plan-readwrite-splitting-rule.md | 15 +-
.../prompts/plan-readwrite-splitting-status.md | 9 +-
...dwriteSplittingToolDescriptorValidatorTest.java | 46 +++
.../handler/ReadwriteSplittingToolHandlerTest.java | 33 +-
...ittingAlgorithmPropertyTemplateServiceTest.java | 52 ++++
...plittingAlgorithmRecommendationServiceTest.java | 62 ++++
...dwriteSplittingWorkflowPlanningServiceTest.java | 35 +++
.../ShadowDefaultAlgorithmWorkflowRequest.java | 5 +
.../tool/model/ShadowRuleWorkflowRequest.java | 5 +
.../ShadowAlgorithmPropertyTemplateService.java | 58 ++++
.../ShadowAlgorithmRecommendationService.java | 85 +++++
.../tool/service/ShadowInspectionService.java | 3 +-
.../service/ShadowWorkflowPlanningService.java | 55 +++-
.../mcp-descriptors/mcp-descriptor-shadow.yaml | 42 ++-
.../prompts/plan-default-shadow-algorithm.md | 6 +
.../prompts/plan-shadow-algorithm-cleanup.md | 5 +
.../shardingsphere-mcp/prompts/plan-shadow-rule.md | 10 +
.../ShadowToolDescriptorValidatorTest.java | 33 ++
...ShadowAlgorithmPropertyTemplateServiceTest.java | 51 +++
.../ShadowAlgorithmRecommendationServiceTest.java | 61 ++++
.../service/ShadowWorkflowPlanningServiceTest.java | 26 +-
.../sharding/ShardingMCPHandlerProvider.java | 49 +--
.../handler/AbstractShardingResourceHandler.java | 64 ++++
.../handler/ShardingAlgorithmResourceHandler.java | 105 +++++++
.../handler/ShardingGovernanceResourceHandler.java | 96 ++++++
.../handler/ShardingStrategyResourceHandler.java | 127 ++++++++
.../handler/ShardingTableResourceHandler.java | 115 +++++++
.../AbstractShardingPlanningToolHandler.java | 114 +------
.../PlanShardingDefaultStrategyToolHandler.java | 18 +-
...PlanShardingKeyGenerateStrategyToolHandler.java | 18 +-
.../PlanShardingKeyGeneratorToolHandler.java | 18 +-
...lanShardingRuleComponentCleanupToolHandler.java | 18 +-
.../PlanShardingTableReferenceRuleToolHandler.java | 18 +-
.../handler/PlanShardingTableRuleToolHandler.java | 18 +-
.../handler/ShardingPlanningRequestBinder.java | 265 ++++++++++++++++
.../AbstractShardingSpecificWorkflowRequest.java | 31 ++
.../ShardingDefaultStrategyWorkflowRequest.java | 42 +++
...ShardingKeyGenerateStrategyWorkflowRequest.java | 42 +++
.../model/ShardingKeyGeneratorWorkflowRequest.java | 42 +++
...hardingRuleComponentCleanupWorkflowRequest.java | 42 +++
.../ShardingTableReferenceRuleWorkflowRequest.java | 42 +++
.../model/ShardingTableRuleWorkflowRequest.java | 42 +++
.../tool/model/ShardingWorkflowRequest.java | 5 +
.../ShardingAlgorithmPropertyTemplateService.java | 78 +++++
.../ShardingAlgorithmRecommendationService.java | 131 ++++++++
...dingDefaultStrategyWorkflowPlanningService.java | 76 +++++
.../tool/service/ShardingInspectionService.java | 3 +-
...KeyGenerateStrategyWorkflowPlanningService.java | 76 +++++
...hardingKeyGeneratorWorkflowPlanningService.java | 76 +++++
...uleComponentCleanupWorkflowPlanningService.java | 76 +++++
...gTableReferenceRuleWorkflowPlanningService.java | 76 +++++
.../ShardingTableRuleWorkflowPlanningService.java | 76 +++++
...ce.java => ShardingWorkflowPlanningKernel.java} | 93 +++++-
.../service/ShardingWorkflowPlanningService.java | 341 ++-------------------
.../mcp-descriptors/mcp-descriptor-sharding.yaml | 62 ++++
.../prompts/plan-sharding-default-strategy.md | 6 +
.../prompts/plan-sharding-key-generate-strategy.md | 6 +
.../prompts/plan-sharding-key-generator.md | 6 +
.../plan-sharding-rule-component-cleanup.md | 5 +
.../prompts/plan-sharding-table-reference-rule.md | 5 +
.../prompts/plan-sharding-table-rule.md | 6 +
.../sharding/ShardingMCPHandlerProviderTest.java | 8 +
.../ShardingToolDescriptorValidatorTest.java | 38 +++
.../ShardingAlgorithmResourceHandlerTest.java | 89 ++++++
.../ShardingGovernanceResourceHandlerTest.java | 85 +++++
.../ShardingStrategyResourceHandlerTest.java | 98 ++++++
.../handler/ShardingTableResourceHandlerTest.java | 93 ++++++
.../handler/ShardingPlanningRequestBinderTest.java | 91 ++++++
...hardingDefaultStrategyWorkflowRequestTest.java} | 32 +-
...dingKeyGenerateStrategyWorkflowRequestTest.java | 41 +++
.../ShardingKeyGeneratorWorkflowRequestTest.java} | 30 +-
...ngRuleComponentCleanupWorkflowRequestTest.java} | 31 +-
...dingTableReferenceRuleWorkflowRequestTest.java} | 31 +-
.../ShardingTableRuleWorkflowRequestTest.java} | 31 +-
...ardingAlgorithmPropertyTemplateServiceTest.java | 57 ++++
...ShardingAlgorithmRecommendationServiceTest.java | 66 ++++
...DefaultStrategyWorkflowPlanningServiceTest.java | 54 ++++
...enerateStrategyWorkflowPlanningServiceTest.java | 54 ++++
...ingKeyGeneratorWorkflowPlanningServiceTest.java | 54 ++++
...omponentCleanupWorkflowPlanningServiceTest.java | 54 ++++
...leReferenceRuleWorkflowPlanningServiceTest.java | 57 ++++
...ardingTableRuleWorkflowPlanningServiceTest.java | 54 ++++
.../ShardingWorkflowPlanningServiceTest.java | 20 ++
.../descriptor/MCPDescriptorCatalogValidator.java | 63 +++-
.../MCPDescriptorCatalogValidatorTest.java | 48 +++
.../descriptor/MCPPromptTemplateLoaderTest.java | 6 +
.../fixture-mixed-placeholder-and-uri-template.md | 1 +
.../prompts/fixture-single-brace-placeholder.md | 1 +
.../prompts/fixture-uri-template.md | 1 +
.../model-contract/capabilities.yaml | 17 +-
109 files changed, 4235 insertions(+), 599 deletions(-)
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.cn.md b/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.cn.md
index 3823bfcfca2..286470012a3 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.cn.md
@@ -22,6 +22,8 @@ weight = 4
- 确认计划语句是 `CREATE BROADCAST TABLE RULE` 或 `DROP BROADCAST TABLE RULE`。
- 确认所有表名都是逻辑表名。
+- 执行 workflow 前,确认返回的 `plan_id`、`resources_to_read`、`next_actions` 和 `distsql_artifacts`。
+- 广播表规划不需要算法推荐或算法属性要求。
- 执行前先预览 workflow,执行后校验 Proxy 可见规则状态。
规则变更的通用审查流程见[规则变更流程](../plugin-workflow/)。
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.en.md b/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.en.md
index 211c7669e49..33cd96dcf3c 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/broadcast.en.md
@@ -22,6 +22,8 @@ It generates broadcast rule DistSQL only. It does not generate physical table DD
- Confirm that the planned statement is `CREATE BROADCAST TABLE RULE` or `DROP BROADCAST TABLE RULE`.
- Confirm that all table names are logical table names.
+- Confirm the returned `plan_id`, `resources_to_read`, `next_actions`, and `distsql_artifacts` before applying the workflow.
+- Broadcast planning does not require algorithm recommendations or property requirements.
- Preview the workflow before execution and validate Proxy-visible rule state after execution.
For the general review flow of rule changes, see [Rule Change Flow](../plugin-workflow/).
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.cn.md b/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.cn.md
index 4bf83a0c96e..0828a898a3c 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.cn.md
@@ -24,6 +24,19 @@ weight = 1
| 执行变更 | 确认自动执行,或导出人工执行包后由运维执行。 | 有副作用的变更必须经过确认。 |
| 校验结果 | 执行后按功能插件返回规则状态或 workflow 执行结果。 | 确认变更是否生效。 |
+## 计划响应字段
+
+规划工具会返回 `plan_id`,用于把已生成计划连接到 workflow resource、预览、执行和校验工具。
+执行前应先通过 workflow resource 审查已持久化的计划。
+当用户需要再次确认时,应先预览再执行。
+
+面向模型的规划响应可能包含:
+
+- `algorithm_recommendations`:根据 Proxy 可见插件目录或用户显式输入选择的候选算法。
+- `property_requirements`:所选算法的必填或可选属性;缺少必填属性时,workflow 会保持在澄清状态,而不是生成不安全产物。
+- `resources_to_read` 和 `next_actions`:继续 workflow 所需的资源和工具导航提示。
+- `distsql_artifacts`:在当前功能插件边界内生成的可审查规则 DistSQL。
+
## 变更执行选择
| 用户说法 | 用户会得到什么 | 关注点 |
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.en.md b/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.en.md
index 3d0f1e8f6b8..acf6c478275 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/plugin-workflow.en.md
@@ -24,6 +24,19 @@ It is not a standalone business feature. Users usually enter this flow from a co
| Apply the change | Confirm automatic execution, or export a manual package for operators. | Side-effecting changes must be confirmed. |
| Validate the result | Check rule state or workflow execution results returned by the feature plugin. | Confirm that the change has taken effect. |
+## Plan Response Fields
+
+Planning tools return a `plan_id` that links the generated plan to workflow resources, preview, apply, and validation tools.
+Use the workflow resource to review the persisted plan before applying it.
+Use preview before execution when the user needs one more confirmation step.
+
+Model-facing planning responses may include:
+
+- `algorithm_recommendations`: candidate algorithms selected from Proxy-visible plugin catalogs or explicit user input.
+- `property_requirements`: required or optional properties for the selected algorithms. Missing required properties keep the workflow in clarification instead of generating unsafe artifacts.
+- `resources_to_read` and `next_actions`: resource and tool navigation hints for continuing the workflow.
+- `distsql_artifacts`: reviewable rule DistSQL generated inside the current feature plugin boundary.
+
## Change Execution Choices
| User wording | What users receive | Focus |
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.cn.md b/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.cn.md
index 7c9c087efaa..364b8b160b1 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.cn.md
@@ -23,6 +23,9 @@ weight = 5
- 确认规则计划使用 `CREATE`、`ALTER` 或 `DROP READWRITE_SPLITTING RULE`。
- 确认状态计划使用 `ALTER READWRITE_SPLITTING RULE ... ENABLE` 或 `DISABLE`。
- 确认存储单元名是已有逻辑存储单元,workflow 不会创建它们。
+- 选择负载均衡算法前,审查 `algorithm_recommendations`。
+- `RANDOM` 和 `ROUND_ROBIN` 不需要负载均衡属性;`WEIGHT` 需要为每个读存储单元提供一个权重属性。
+- 执行 workflow 前,确认返回的 `plan_id`、`resources_to_read`、`next_actions` 和 `distsql_artifacts`。
规则变更的通用审查流程见[规则变更流程](../plugin-workflow/)。
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.en.md b/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.en.md
index 0ecf9711a98..9bd62758577 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/readwrite-splitting.en.md
@@ -23,6 +23,9 @@ It generates readwrite-splitting DistSQL only. It does not generate storage unit
- Confirm that rule plans use `CREATE`, `ALTER`, or `DROP READWRITE_SPLITTING RULE`.
- Confirm that status plans use `ALTER READWRITE_SPLITTING RULE ... ENABLE` or `DISABLE`.
- Confirm that storage unit names are existing logical storage units and are not created by the workflow.
+- Review `algorithm_recommendations` before selecting a load-balance algorithm.
+- `RANDOM` and `ROUND_ROBIN` do not require load-balance properties. `WEIGHT` requires one property per read storage unit.
+- Confirm the returned `plan_id`, `resources_to_read`, `next_actions`, and `distsql_artifacts` before applying the workflow.
For the general review flow of rule changes, see [Rule Change Flow](../plugin-workflow/).
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.cn.md b/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.cn.md
index 9520803a760..15784ea2f3a 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.cn.md
@@ -22,7 +22,10 @@ weight = 6
- 确认规则计划使用影子 DistSQL,并引用已有存储单元。
- 确认默认算法计划只修改默认影子算法。
+- 选择影子算法前,审查 `algorithm_recommendations`;`SQL_HINT` 没有必填属性。
+- `VALUE_MATCH` 需要 `operation`、`column` 和 `value`;`REGEX_MATCH` 需要 `operation`、`column` 和 `regex`。
- 确认清理计划包含目标算法未被使用的证据。
+- 执行 workflow 前,确认返回的 `plan_id`、`resources_to_read`、`next_actions` 和 `distsql_artifacts`。
规则变更的通用审查流程见[规则变更流程](../plugin-workflow/)。
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.en.md b/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.en.md
index 4f68fcf5f8d..aba51321145 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/shadow.en.md
@@ -22,7 +22,10 @@ It generates shadow rule DistSQL only. It does not create shadow databases or ph
- Confirm that rule plans use shadow DistSQL and reference existing storage units.
- Confirm that default algorithm plans update only the default shadow algorithm.
+- Review `algorithm_recommendations` before selecting a shadow algorithm. `SQL_HINT` has no required properties.
+- `VALUE_MATCH` requires `operation`, `column`, and `value`; `REGEX_MATCH` requires `operation`, `column`, and `regex`.
- Confirm cleanup plans include evidence that the target algorithm is unused.
+- Confirm the returned `plan_id`, `resources_to_read`, `next_actions`, and `distsql_artifacts` before applying the workflow.
For the general review flow of rule changes, see [Rule Change Flow](../plugin-workflow/).
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.cn.md b/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.cn.md
index 17f529c7f60..472f77be6a8 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.cn.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.cn.md
@@ -22,8 +22,11 @@ weight = 7
## 审查重点
- 确认表规则计划只使用分片规则 DistSQL 和逻辑对象名。
+- 审查分片 `algorithm_recommendations` 和 `property_requirements`;例如 inline 算法需要 `algorithm-expression`,MOD 类算法需要 `sharding-count`。
- 确认键生成器和键生成策略计划不会把键生成器属性与分片算法属性混用。
+- 键生成器推荐需要和分片算法推荐分开审查;Snowflake 属性是可选项,除非部署要求固定值。
- 确认清理计划包含 unused-state 和 used-by 检查。
+- 执行 workflow 前,确认返回的 `plan_id`、`resources_to_read`、`next_actions` 和 `distsql_artifacts`。
- 执行前先预览 workflow,执行后校验 Proxy 可见规则状态。
规则变更的通用审查流程见[规则变更流程](../plugin-workflow/)。
diff --git a/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.en.md b/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.en.md
index f56270013fa..8222e5c0dd9 100644
--- a/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.en.md
+++ b/docs/document/content/user-manual/shardingsphere-mcp/features/sharding.en.md
@@ -22,8 +22,11 @@ It generates sharding rule DistSQL only. It does not generate physical DDL, inde
## Review checklist
- Confirm that table rule plans use only sharding rule DistSQL and logical identifiers.
+- Review sharding `algorithm_recommendations` and `property_requirements`; for example, inline algorithms require `algorithm-expression`, and MOD-style algorithms require `sharding-count`.
- Confirm that key generator and key generate strategy plans keep generator properties separate from sharding algorithm properties.
+- Review key-generator recommendations separately from sharding algorithm recommendations. Snowflake properties are optional unless the deployment requires fixed values.
- Confirm cleanup plans include unused-state and used-by checks.
+- Confirm the returned `plan_id`, `resources_to_read`, `next_actions`, and `distsql_artifacts` before applying the workflow.
- Preview the workflow before execution and validate Proxy-visible rule state after execution.
For the general review flow of rule changes, see [Rule Change Flow](../plugin-workflow/).
diff --git a/mcp/features/broadcast/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-broadcast-rule.md b/mcp/features/broadcast/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-broadcast-rule.md
index d09bed79118..29b24314c84 100644
--- a/mcp/features/broadcast/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-broadcast-rule.md
+++ b/mcp/features/broadcast/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-broadcast-rule.md
@@ -2,6 +2,13 @@
Use this prompt when the user wants to create or drop ShardingSphere broadcast table rules through DistSQL.
+Inputs:
+- database: {{database}}
+- table: {{table}}
+- tables: {{tables}}
+- operation_type: {{operation_type}}
+- plan_id: {{plan_id}}
+
1. Read `shardingsphere://features/broadcast/databases/{database}/rules` before planning.
2. Ask for `database` and `tables` when they are missing.
3. Use `database_gateway_plan_broadcast_rule` with `operation_type=create` or `operation_type=drop`.
diff --git a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/descriptor/BroadcastToolDescriptorValidatorTest.java b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/descriptor/BroadcastToolDescriptorValidatorTest.java
index 979421ecbd8..9a36c5c5a08 100644
--- a/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/descriptor/BroadcastToolDescriptorValidatorTest.java
+++ b/mcp/features/broadcast/src/test/java/org/apache/shardingsphere/mcp/feature/broadcast/descriptor/BroadcastToolDescriptorValidatorTest.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.api.prompt.descriptor.MCPPromptDescriptor;
import org.apache.shardingsphere.mcp.api.tool.descriptor.MCPToolDescriptor;
import org.apache.shardingsphere.mcp.feature.broadcast.BroadcastFeatureDefinition;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPPromptTemplateLoader;
import org.apache.shardingsphere.mcp.support.descriptor.MCPShardingSphereMetadataKeys;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -100,6 +101,18 @@ class BroadcastToolDescriptorValidatorTest {
assertTrue(prompt.contains("storage unit mutation operations"));
}
+ @Test
+ void assertPromptRendersPlanningContext() throws IOException {
+ String actual = MCPPromptTemplateLoader.render(readResource("META-INF/shardingsphere-mcp/prompts/plan-broadcast-rule.md"), Map.of(
+ "database", "logic_db", "table", "t_order", "tables", "t_order,t_order_item", "operation_type", "create", "plan_id", "plan-1"));
+ assertTrue(actual.contains("- database: logic_db"));
+ assertTrue(actual.contains("- table: t_order"));
+ assertTrue(actual.contains("- tables: t_order,t_order_item"));
+ assertTrue(actual.contains("- operation_type: create"));
+ assertTrue(actual.contains("- plan_id: plan-1"));
+ assertFalse(actual.contains("{{"));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("requiredOutputFields")
@SuppressWarnings("unchecked")
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java
index 0553976d305..80cbc0b5f04 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/PlanReadwriteSplittingRuleToolHandler.java
@@ -30,7 +30,10 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanPayloa
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningArguments;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRequestBinder;
+import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -82,6 +85,7 @@ public final class PlanReadwriteSplittingRuleToolHandler implements MCPToolHandl
applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request::setReadStorageUnits);
applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.TRANSACTIONAL_READ_QUERY_STRATEGY_FIELD, request::setTransactionalReadQueryStrategy);
applyStringField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_TYPE_FIELD, request::setLoadBalancerType);
+ applyMapField(structuredIntentEvidence, ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_PROPERTIES_FIELD, request::putLoadBalancerProperties);
}
private void applyUserOverrides(final ReadwriteSplittingRuleWorkflowRequest request, final Map<String, Object> userOverrides) {
@@ -96,9 +100,27 @@ public final class PlanReadwriteSplittingRuleToolHandler implements MCPToolHandl
}
private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
- final String value = workflowPlanningArguments.getStringArgument(fieldName);
+ String value = workflowPlanningArguments.getStringArgument(fieldName);
if (!value.isEmpty()) {
consumer.accept(value);
}
}
+
+ private void applyMapField(final Map<String, Object> values, final String fieldName, final Consumer<Map<String, String>> consumer) {
+ Object value = values.get(fieldName);
+ if (value instanceof Map) {
+ consumer.accept(createStringMap((Map<?, ?>) value));
+ }
+ }
+
+ private Map<String, String> createStringMap(final Map<?, ?> values) {
+ Map<String, String> result = new LinkedHashMap<>(values.size(), 1F);
+ for (Entry<?, ?> entry : values.entrySet()) {
+ String key = Objects.toString(entry.getKey(), "").trim();
+ if (!key.isEmpty()) {
+ result.put(key, Objects.toString(entry.getValue(), "").trim());
+ }
+ }
+ return result;
+ }
}
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequest.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequest.java
index 2fbad928244..b9305158860 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequest.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/model/ReadwriteSplittingRuleWorkflowRequest.java
@@ -114,6 +114,11 @@ public final class ReadwriteSplittingRuleWorkflowRequest extends WorkflowRequest
}
}
+ @Override
+ public Map<String, String> getAlgorithmProperties(final String algorithmRole) {
+ return "primary".equals(algorithmRole) ? loadBalancerProperties : super.getAlgorithmProperties(algorithmRole);
+ }
+
@Override
public ReadwriteSplittingRuleWorkflowRequest copy() {
ReadwriteSplittingRuleWorkflowRequest result = copyTo(new ReadwriteSplittingRuleWorkflowRequest());
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmPropertyTemplateService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmPropertyTemplateService.java
new file mode 100644
index 00000000000..ea60405dd6b
--- /dev/null
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmPropertyTemplateService.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Readwrite-splitting load-balance algorithm property template service.
+ */
+public final class ReadwriteSplittingAlgorithmPropertyTemplateService {
+
+ /**
+ * Find load-balance property requirements.
+ *
+ * @param algorithmType load-balance algorithm type
+ * @param readStorageUnits read storage units
+ * @return property requirements
+ */
+ public List<AlgorithmPropertyRequirement> findRequirements(final String algorithmType, final Collection<String> readStorageUnits) {
+ List<AlgorithmPropertyRequirement> result = new LinkedList<>();
+ if (!"WEIGHT".equals(Objects.toString(algorithmType, "").trim().toUpperCase(Locale.ENGLISH))) {
+ return result;
+ }
+ for (String each : readStorageUnits) {
+ if (!Objects.toString(each, "").trim().isEmpty()) {
+ result.add(new AlgorithmPropertyRequirement("primary", each, true, false,
+ "Relative read traffic weight for this read storage unit.", ""));
+ }
+ }
+ return result;
+ }
+}
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationService.java
new file mode 100644
index 00000000000..6d352a9c500
--- /dev/null
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationService.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.model.ReadwriteSplittingRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Readwrite-splitting load-balance algorithm recommendation service.
+ */
+public final class ReadwriteSplittingAlgorithmRecommendationService {
+
+ /**
+ * Recommend load-balance algorithms.
+ *
+ * @param request workflow request
+ * @param algorithmRows load-balance algorithm rows
+ * @param issues workflow issues
+ * @return selected candidates
+ */
+ public List<AlgorithmCandidate> recommendLoadBalanceAlgorithms(final ReadwriteSplittingRuleWorkflowRequest request,
+ final List<Map<String, Object>> algorithmRows, final List<WorkflowIssue> issues) {
+ List<Map<String, Object>> actualAlgorithmRows = null == algorithmRows ? List.of() : algorithmRows;
+ String actualAlgorithmType = request.getLoadBalancerType().toUpperCase(Locale.ENGLISH);
+ if (!actualAlgorithmType.isEmpty()) {
+ if (actualAlgorithmRows.isEmpty() || containsAlgorithm(actualAlgorithmRows, actualAlgorithmType)) {
+ return List.of(createCandidate(actualAlgorithmType, 100, "User specified load-balance algorithm."));
+ }
+ issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+ String.format("Load-balance algorithm `%s` is not visible from the current Proxy.", actualAlgorithmType),
+ "Choose an available load-balance algorithm.", false, Map.of()));
+ return List.of();
+ }
+ String recommendedType = resolveRecommendedAlgorithm(actualAlgorithmRows);
+ if (recommendedType.isEmpty()) {
+ issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+ "No load-balance algorithm is available from the current Proxy.", "Install or expose at least one load-balance algorithm.", false, Map.of()));
+ return List.of();
+ }
+ return List.of(createCandidate(recommendedType, 90, "Recommended from current load-balance algorithm availability."));
+ }
+
+ private AlgorithmCandidate createCandidate(final String algorithmType, final int score, final String reason) {
+ return new AlgorithmCandidate("primary", algorithmType, null, null, null, score, reason, "");
+ }
+
+ private String resolveRecommendedAlgorithm(final List<Map<String, Object>> algorithmRows) {
+ for (String each : List.of("ROUND_ROBIN", "RANDOM", "WEIGHT")) {
+ if (containsAlgorithm(algorithmRows, each)) {
+ return each;
+ }
+ }
+ return algorithmRows.isEmpty() ? "" : getAlgorithmType(algorithmRows.getFirst());
+ }
+
+ private boolean containsAlgorithm(final List<Map<String, Object>> algorithmRows, final String algorithmType) {
+ return algorithmRows.stream().map(this::getAlgorithmType).anyMatch(algorithmType::equals);
+ }
+
+ private String getAlgorithmType(final Map<String, Object> algorithmRow) {
+ return Objects.toString(algorithmRow.getOrDefault("type", algorithmRow.getOrDefault("name", "")), "").trim().toUpperCase(Locale.ENGLISH);
+ }
+}
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionService.java
index 369f94e0f16..35baff32663 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingInspectionService.java
@@ -103,7 +103,8 @@ public final class ReadwriteSplittingInspectionService {
private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
try {
- return queryFacade.queryWithAnyDatabase("SHOW LOAD BALANCE ALGORITHM PLUGINS");
+ List<Map<String, Object>> result = queryFacade.queryWithAnyDatabase("SHOW LOAD BALANCE ALGORITHM PLUGINS");
+ return null == result ? List.of(Map.of("type", "RANDOM"), Map.of("type", "ROUND_ROBIN"), Map.of("type", "WEIGHT")) : result;
} catch (final MCPQueryFailedException ex) {
if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
return List.of(Map.of("type", "RANDOM"), Map.of("type", "ROUND_ROBIN"), Map.of("type", "WEIGHT"));
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
index 3b2fc3f6ce1..d71b0f52a84 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
@@ -21,6 +21,8 @@ import org.apache.shardingsphere.mcp.feature.readwritesplitting.ReadwriteSplitti
import org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.model.ReadwriteSplittingRuleWorkflowRequest;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
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.WorkflowIssue;
@@ -57,16 +59,24 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
private final ReadwriteSplittingInspectionService inspectionService;
+ private final ReadwriteSplittingAlgorithmRecommendationService algorithmRecommendationService;
+
+ private final ReadwriteSplittingAlgorithmPropertyTemplateService algorithmPropertyTemplateService;
+
private final ReadwriteSplittingRuleDistSQLPlanningService distSQLPlanningService;
public ReadwriteSplittingRuleWorkflowPlanningService() {
inspectionService = new ReadwriteSplittingInspectionService();
+ algorithmRecommendationService = new ReadwriteSplittingAlgorithmRecommendationService();
+ algorithmPropertyTemplateService = new ReadwriteSplittingAlgorithmPropertyTemplateService();
distSQLPlanningService = new ReadwriteSplittingRuleDistSQLPlanningService();
}
ReadwriteSplittingRuleWorkflowPlanningService(final ReadwriteSplittingInspectionService inspectionService,
final ReadwriteSplittingRuleDistSQLPlanningService distSQLPlanningService) {
this.inspectionService = inspectionService;
+ algorithmRecommendationService = new ReadwriteSplittingAlgorithmRecommendationService();
+ algorithmPropertyTemplateService = new ReadwriteSplittingAlgorithmPropertyTemplateService();
this.distSQLPlanningService = distSQLPlanningService;
}
@@ -93,6 +103,13 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
if (!ensureLifecycleState(clarifiedIntent, mergedRequest, existingRules, result, databaseType)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
}
+ if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(clarifiedIntent.getOperationType())) {
+ planAlgorithms(queryFacade, mergedRequest, result);
+ if (!mergedRequest.getLoadBalancerType().isEmpty() && !planningSupport.isReadyForArtifactPlanning(mergedRequest, clarifiedIntent, result, findPropertyRequirements(mergedRequest),
+ "Please use a load-balance algorithm visible in the current Proxy and provide required properties.")) {
+ return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
+ }
+ }
addRuleArtifact(result, mergedRequest, clarifiedIntent.getOperationType());
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_REVIEW, WorkflowLifecycle.STATUS_PLANNED);
}
@@ -196,6 +213,19 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
return rules.stream().anyMatch(each -> WorkflowSQLUtils.isSameIdentifier(databaseType, ruleName, WorkflowRuleValueUtils.getRuleValue(each, "name")));
}
+ private void planAlgorithms(final MCPFeatureQueryFacade queryFacade, final ReadwriteSplittingRuleWorkflowRequest request, final WorkflowContextSnapshot snapshot) {
+ List<AlgorithmCandidate> algorithmCandidates = algorithmRecommendationService.recommendLoadBalanceAlgorithms(
+ request, inspectionService.queryLoadBalanceAlgorithmPlugins(queryFacade), snapshot.getIssues());
+ snapshot.getAlgorithmCandidates().addAll(algorithmCandidates);
+ if (!request.getLoadBalancerType().isEmpty() && !algorithmCandidates.isEmpty()) {
+ request.setLoadBalancerType(algorithmCandidates.getFirst().getAlgorithmType());
+ }
+ }
+
+ private List<AlgorithmPropertyRequirement> findPropertyRequirements(final ReadwriteSplittingRuleWorkflowRequest request) {
+ return algorithmPropertyTemplateService.findRequirements(request.getLoadBalancerType(), request.getReadStorageUnits());
+ }
+
private void addRuleArtifact(final WorkflowContextSnapshot snapshot, final ReadwriteSplittingRuleWorkflowRequest request, final String operationType) {
if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
snapshot.getRuleArtifacts().add(distSQLPlanningService.planDropRule(request.getRuleName()));
diff --git a/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-readwrite-splitting.yaml b/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-readwrite-splitting.yaml
index 8852c63d9f6..38195509ac4 100644
--- a/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-readwrite-splitting.yaml
+++ b/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-readwrite-splitting.yaml
@@ -158,6 +158,10 @@ prompts:
title: Load Balancer Type
description: "Optional load-balance algorithm type."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a readwrite-splitting rule plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_readwrite_splitting_rule
@@ -194,6 +198,10 @@ prompts:
title: Target Status
description: "Target status: enable or disable."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a readwrite-splitting status plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_readwrite_splitting_status
@@ -250,7 +258,8 @@ resourceNavigation:
- from: shardingsphere://features/readwrite-splitting/load-balance-algorithm-plugins
to: database_gateway_plan_readwrite_splitting_rule
requiredArguments: []
- carriedArguments: []
+ carriedArguments:
+ - load_balancer_type
description: "Choose an available load-balance algorithm before planning create or alter."
- from: database_gateway_plan_readwrite_splitting_rule
to: shardingsphere://workflows/{plan_id}
@@ -259,6 +268,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning readwrite-splitting rule changes."
+ - from: database_gateway_plan_readwrite_splitting_rule
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed readwrite-splitting rule workflow after user approval."
- from: database_gateway_plan_readwrite_splitting_status
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -266,6 +282,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning readwrite-splitting status changes."
+ - from: database_gateway_plan_readwrite_splitting_status
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed readwrite-splitting status workflow after user approval."
tools:
- name: database_gateway_plan_readwrite_splitting_rule
title: Plan Readwrite-Splitting Rule
@@ -330,6 +353,11 @@ tools:
load_balancer_type:
type: string
description: "Inferred load-balance algorithm type."
+ load_balancer_properties:
+ type: object
+ description: "Inferred load-balance algorithm properties."
+ additionalProperties:
+ type: string
required: []
additionalProperties: false
delivery_mode:
@@ -363,6 +391,11 @@ tools:
load_balancer_type:
type: string
description: "Load-balance algorithm type override."
+ load_balancer_properties:
+ type: object
+ description: "Load-balance algorithm properties override."
+ additionalProperties:
+ type: string
required: []
additionalProperties: false
required: []
diff --git a/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-rule.md b/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-rule.md
index f2f6ee7fe27..981b8220198 100644
--- a/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-rule.md
+++ b/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-rule.md
@@ -1,13 +1,14 @@
Plan a ShardingSphere readwrite-splitting rule workflow.
Inputs:
-- database: {database}
-- rule: {rule}
-- operation_type: {operation_type}
-- write_storage_unit: {write_storage_unit}
-- read_storage_units: {read_storage_units}
-- transactional_read_query_strategy: {transactional_read_query_strategy}
-- load_balancer_type: {load_balancer_type}
+- database: {{database}}
+- rule: {{rule}}
+- operation_type: {{operation_type}}
+- write_storage_unit: {{write_storage_unit}}
+- read_storage_units: {{read_storage_units}}
+- transactional_read_query_strategy: {{transactional_read_query_strategy}}
+- load_balancer_type: {{load_balancer_type}}
+- plan_id: {{plan_id}}
Before calling the planning tool, read current rule state from shardingsphere://features/readwrite-splitting/databases/{database}/rules and read the load-balance algorithm catalog when algorithm choice is unclear.
Ask the user for missing rule name, write storage unit, read storage units, transactional read query strategy, or algorithm properties instead of guessing.
diff --git a/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-status.md b/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-status.md
index e43016bb64d..5949a60f111 100644
--- a/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-status.md
+++ b/mcp/features/readwrite-splitting/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-status.md
@@ -1,10 +1,11 @@
Plan a ShardingSphere readwrite-splitting storage-unit status workflow.
Inputs:
-- database: {database}
-- rule: {rule}
-- storage_unit: {storage_unit}
-- target_status: {target_status}
+- database: {{database}}
+- rule: {{rule}}
+- storage_unit: {{storage_unit}}
+- target_status: {{target_status}}
+- plan_id: {{plan_id}}
Before calling the planning tool, read current status from shardingsphere://features/readwrite-splitting/databases/{database}/rules/{rule}/status.
Ask the user for the rule name, read storage unit, or target status when any input is missing.
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/descriptor/ReadwriteSplittingToolDescriptorValidatorTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/descriptor/ReadwriteSplittingToolDescriptorValidatorTest.java
index 47ccffc3242..5b56a4a22f3 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/descriptor/ReadwriteSplittingToolDescriptorValidatorTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/descriptor/ReadwriteSplittingToolDescriptorValidatorTest.java
@@ -22,6 +22,8 @@ import org.apache.shardingsphere.mcp.api.tool.descriptor.MCPToolDescriptor;
import org.apache.shardingsphere.mcp.feature.readwritesplitting.ReadwriteSplittingFeatureDefinition;
import org.apache.shardingsphere.mcp.support.descriptor.MCPCompletionTargetDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPPromptTemplateLoader;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPResourceNavigationDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPShardingSphereMetadataKeys;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -112,6 +114,34 @@ class ReadwriteSplittingToolDescriptorValidatorTest {
assertTrue(prompt.contains("physical metadata probes"));
}
+ @Test
+ void assertPromptsRenderPlanningContext() throws IOException {
+ String actualRulePrompt = MCPPromptTemplateLoader.render(readResource("META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-rule.md"), Map.of(
+ "database", "logic_db", "rule", "readwrite_ds", "operation_type", "create", "write_storage_unit", "write_ds", "read_storage_units", "read_ds_0",
+ "transactional_read_query_strategy", "DYNAMIC", "load_balancer_type", "ROUND_ROBIN", "plan_id", "plan-1"));
+ assertTrue(actualRulePrompt.contains("- database: logic_db"));
+ assertTrue(actualRulePrompt.contains("- plan_id: plan-1"));
+ assertFalse(actualRulePrompt.contains("{{"));
+ String actualStatusPrompt = MCPPromptTemplateLoader.render(readResource("META-INF/shardingsphere-mcp/prompts/plan-readwrite-splitting-status.md"), Map.of(
+ "database", "logic_db", "rule", "readwrite_ds", "storage_unit", "read_ds_0", "target_status", "enable", "plan_id", "plan-1"));
+ assertTrue(actualStatusPrompt.contains("- storage_unit: read_ds_0"));
+ assertTrue(actualStatusPrompt.contains("- plan_id: plan-1"));
+ assertFalse(actualStatusPrompt.contains("{{"));
+ }
+
+ @Test
+ void assertPlanToolsNavigateToWorkflowAndApply() {
+ assertPlanToolNavigation(ReadwriteSplittingFeatureDefinition.PLAN_RULE_TOOL_NAME);
+ assertPlanToolNavigation(ReadwriteSplittingFeatureDefinition.PLAN_STATUS_TOOL_NAME);
+ }
+
+ @Test
+ void assertAlgorithmResourceNavigationCarriesLoadBalancerType() {
+ MCPResourceNavigationDescriptor actual = findNavigation(ReadwriteSplittingFeatureDefinition.LOAD_BALANCE_ALGORITHM_PLUGINS_RESOURCE_URI,
+ ReadwriteSplittingFeatureDefinition.PLAN_RULE_TOOL_NAME);
+ assertThat(actual.getCarriedArguments(), is(List.of(ReadwriteSplittingFeatureDefinition.LOAD_BALANCER_TYPE_FIELD)));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("requiredOutputFields")
@SuppressWarnings("unchecked")
@@ -162,6 +192,22 @@ class ReadwriteSplittingToolDescriptorValidatorTest {
.anyMatch(each -> referenceType.equals(each.getReferenceType()) && reference.equals(each.getReference()));
}
+ private void assertPlanToolNavigation(final String toolName) {
+ assertNavigation(toolName, "shardingsphere://workflows/{plan_id}");
+ assertNavigation(toolName, "database_gateway_apply_workflow");
+ }
+
+ private void assertNavigation(final String from, final String to) {
+ MCPResourceNavigationDescriptor actual = findNavigation(from, to);
+ assertThat(actual.getRequiredArguments(), is(List.of("plan_id")));
+ assertThat(actual.getCarriedArguments(), is(List.of("plan_id")));
+ }
+
+ private MCPResourceNavigationDescriptor findNavigation(final String from, final String to) {
+ return MCPDescriptorCatalogIndex.getResourceNavigationDescriptors(from).stream()
+ .filter(each -> to.equals(each.getTo())).findFirst().orElseThrow();
+ }
+
private String readResource(final String resourceName) throws IOException {
try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/ReadwriteSplittingToolHandlerTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/ReadwriteSplittingToolHandlerTest.java
index 0216d8ffcd4..310ec045ff4 100644
--- a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/ReadwriteSplittingToolHandlerTest.java
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/handler/ReadwriteSplittingToolHandlerTest.java
@@ -71,6 +71,27 @@ class ReadwriteSplittingToolHandlerTest {
assertThat(requestCaptor.getValue().getReadStorageUnits(), is(List.of("read_ds_1")));
}
+ @Test
+ void assertHandlePlanRuleWithTopLevelLoadBalancerProperties() {
+ ReadwriteSplittingRuleWorkflowRequest actual = handlePlanRule(Map.of("database", "logic_db",
+ "load_balancer_properties", Map.of("read_ds_0", "2")));
+ assertThat(actual.getLoadBalancerProperties(), is(Map.of("read_ds_0", "2")));
+ }
+
+ @Test
+ void assertHandlePlanRuleWithStructuredIntentLoadBalancerProperties() {
+ ReadwriteSplittingRuleWorkflowRequest actual = handlePlanRule(Map.of("database", "logic_db",
+ "structured_intent_evidence", Map.of("load_balancer_properties", Map.of("read_ds_0", "2"))));
+ assertThat(actual.getLoadBalancerProperties(), is(Map.of("read_ds_0", "2")));
+ }
+
+ @Test
+ void assertHandlePlanRuleWithUserOverrideLoadBalancerProperties() {
+ ReadwriteSplittingRuleWorkflowRequest actual = handlePlanRule(Map.of("database", "logic_db",
+ "user_overrides", Map.of("load_balancer_properties", Map.of("read_ds_0", "2"))));
+ assertThat(actual.getLoadBalancerProperties(), is(Map.of("read_ds_0", "2")));
+ }
+
@Test
void assertHandlePlanRuleWithArtifacts() {
ReadwriteSplittingRuleWorkflowPlanningService planningService = mock(ReadwriteSplittingRuleWorkflowPlanningService.class);
@@ -80,7 +101,7 @@ class ReadwriteSplittingToolHandlerTest {
Map<String, Object> actualPayload = actual.toPayload();
assertFalse(actualPayload.containsKey("ddl_artifacts"));
assertFalse(actualPayload.containsKey("index_plan"));
- assertTrue(String.valueOf(((Map<?, ?>) ((List<?>) actualPayload.get("distsql_artifacts")).get(0)).get("sql")).contains("CREATE READWRITE_SPLITTING RULE"));
+ assertTrue(String.valueOf(((Map<?, ?>) ((List<?>) actualPayload.get("distsql_artifacts")).getFirst()).get("sql")).contains("CREATE READWRITE_SPLITTING RULE"));
assertTrue(extractResourceUris((List<?>) actualPayload.get("resources_to_read")).contains("shardingsphere://features/readwrite-splitting/databases/logic_db/rules"));
assertThat(((Map<?, ?>) actualPayload.get("proxy_topology_hint")).get("expected_runtime_view"), is("proxy_rule_distsql"));
}
@@ -111,6 +132,16 @@ class ReadwriteSplittingToolHandlerTest {
return result;
}
+ private ReadwriteSplittingRuleWorkflowRequest handlePlanRule(final Map<String, Object> arguments) {
+ ReadwriteSplittingRuleWorkflowPlanningService planningService = mock(ReadwriteSplittingRuleWorkflowPlanningService.class);
+ when(planningService.plan(any(), any(), any(), any())).thenReturn(createRuleSnapshot());
+ WorkflowContextFixture fixture = createWorkflowContextFixture();
+ new PlanReadwriteSplittingRuleToolHandler(planningService).handle(fixture.workflowContext, new MCPToolCall("session-1", arguments));
+ ArgumentCaptor<ReadwriteSplittingRuleWorkflowRequest> requestCaptor = ArgumentCaptor.forClass(ReadwriteSplittingRuleWorkflowRequest.class);
+ verify(planningService).plan(eq(fixture.workflowSessionContext), eq(fixture.queryFacade), eq("session-1"), requestCaptor.capture());
+ return requestCaptor.getValue();
+ }
+
private WorkflowContextSnapshot createStatusSnapshot() {
ReadwriteSplittingStatusWorkflowRequest request = new ReadwriteSplittingStatusWorkflowRequest();
request.setDatabase("logic_db");
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmPropertyTemplateServiceTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmPropertyTemplateServiceTest.java
new file mode 100644
index 00000000000..02279bcfdb1
--- /dev/null
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmPropertyTemplateServiceTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ReadwriteSplittingAlgorithmPropertyTemplateServiceTest {
+
+ private final ReadwriteSplittingAlgorithmPropertyTemplateService service = new ReadwriteSplittingAlgorithmPropertyTemplateService();
+
+ @Test
+ void assertFindRequirementsForWeight() {
+ List<AlgorithmPropertyRequirement> actual = service.findRequirements("WEIGHT", List.of("read_ds_0", "read_ds_1"));
+ assertThat(actual.size(), is(2));
+ assertThat(actual.getFirst().getAlgorithmRole(), is("primary"));
+ assertThat(actual.getFirst().getPropertyKey(), is("read_ds_0"));
+ assertTrue(actual.getFirst().isRequired());
+ assertThat(actual.get(1).getPropertyKey(), is("read_ds_1"));
+ }
+
+ @Test
+ void assertFindRequirementsForRandom() {
+ assertTrue(service.findRequirements("RANDOM", List.of("read_ds_0")).isEmpty());
+ }
+
+ @Test
+ void assertFindRequirementsForRoundRobin() {
+ assertTrue(service.findRequirements("ROUND_ROBIN", List.of("read_ds_0")).isEmpty());
+ }
+}
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationServiceTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationServiceTest.java
new file mode 100644
index 00000000000..e24cd281b3d
--- /dev/null
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingAlgorithmRecommendationServiceTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.readwritesplitting.tool.model.ReadwriteSplittingRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.junit.jupiter.api.Test;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ReadwriteSplittingAlgorithmRecommendationServiceTest {
+
+ private final ReadwriteSplittingAlgorithmRecommendationService service = new ReadwriteSplittingAlgorithmRecommendationService();
+
+ @Test
+ void assertRecommendSpecifiedAlgorithm() {
+ ReadwriteSplittingRuleWorkflowRequest request = new ReadwriteSplittingRuleWorkflowRequest();
+ request.setLoadBalancerType("round_robin");
+ List<AlgorithmCandidate> actual = service.recommendLoadBalanceAlgorithms(request, List.of(Map.of("type", "ROUND_ROBIN")), new LinkedList<>());
+ assertThat(actual.getFirst().getAlgorithmType(), is("ROUND_ROBIN"));
+ assertThat(actual.getFirst().getAlgorithmRole(), is("primary"));
+ }
+
+ @Test
+ void assertRecommendDefaultAlgorithm() {
+ ReadwriteSplittingRuleWorkflowRequest request = new ReadwriteSplittingRuleWorkflowRequest();
+ List<AlgorithmCandidate> actual = service.recommendLoadBalanceAlgorithms(request, List.of(Map.of("type", "RANDOM"), Map.of("type", "ROUND_ROBIN")), new LinkedList<>());
+ assertThat(actual.getFirst().getAlgorithmType(), is("ROUND_ROBIN"));
+ }
+
+ @Test
+ void assertRecommendRejectsInvisibleAlgorithm() {
+ ReadwriteSplittingRuleWorkflowRequest request = new ReadwriteSplittingRuleWorkflowRequest();
+ request.setLoadBalancerType("WEIGHT");
+ List<WorkflowIssue> issues = new LinkedList<>();
+ assertTrue(service.recommendLoadBalanceAlgorithms(request, List.of(Map.of("type", "RANDOM")), issues).isEmpty());
+ assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
+ }
+}
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 3e037681b87..5aff369ba01 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
@@ -32,6 +32,7 @@ import java.util.Map;
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.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -50,6 +51,40 @@ class ReadwriteSplittingWorkflowPlanningServiceTest {
assertFalse(actual.getIndexPlans().iterator().hasNext());
}
+ @Test
+ void assertPlanCreateRuleWithLoadBalancerProperties() {
+ ReadwriteSplittingRuleWorkflowRequest request = createRuleRequest("create");
+ request.setLoadBalancerType("WEIGHT");
+ request.putLoadBalancerProperties(Map.of("read_ds_0", "2"));
+ WorkflowContextSnapshot actual = createRuleService().plan(new TestWorkflowSessionContext(), mockRuleQueryFacade(List.of()), "session-1", request);
+ assertThat(actual.getAlgorithmCandidates().getFirst().getAlgorithmType(), is("WEIGHT"));
+ assertThat(actual.getPropertyRequirements().getFirst().getPropertyKey(), is("read_ds_0"));
+ String expectedSQL = "CREATE READWRITE_SPLITTING RULE readwrite_ds (WRITE_STORAGE_UNIT=write_ds, READ_STORAGE_UNITS(read_ds_0), "
+ + "TRANSACTIONAL_READ_QUERY_STRATEGY='DYNAMIC', TYPE(NAME='weight', PROPERTIES('read_ds_0'='2')))";
+ assertThat(actual.getRuleArtifacts().getFirst().getSql(), is(expectedSQL));
+ }
+
+ @Test
+ void assertPlanCreateRuleClarifiesMissingLoadBalancerProperties() {
+ ReadwriteSplittingRuleWorkflowRequest request = createRuleRequest("create");
+ request.setLoadBalancerType("WEIGHT");
+ WorkflowContextSnapshot actual = createRuleService().plan(new TestWorkflowSessionContext(), mockRuleQueryFacade(List.of()), "session-1", request);
+ assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_CLARIFYING));
+ assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.REQUIRED_PROPERTY_MISSING));
+ assertThat(actual.getPropertyRequirements().getFirst().getPropertyKey(), is("read_ds_0"));
+ assertTrue(actual.getRuleArtifacts().isEmpty());
+ }
+
+ @Test
+ void assertPlanCreateRuleWithLoadBalancerRecommendation() {
+ ReadwriteSplittingRuleWorkflowRequest request = createRuleRequest("create");
+ request.setLoadBalancerType("RANDOM");
+ WorkflowContextSnapshot actual = createRuleService().plan(new TestWorkflowSessionContext(), mockRuleQueryFacade(List.of()), "session-1", request);
+ assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_PLANNED));
+ assertThat(actual.getAlgorithmCandidates().getFirst().getAlgorithmType(), is("RANDOM"));
+ assertTrue(actual.getPropertyRequirements().isEmpty());
+ }
+
@Test
void assertPlanAlterRule() {
WorkflowContextSnapshot actual =
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequest.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequest.java
index c9434e0742e..127c7ca97ce 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequest.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowDefaultAlgorithmWorkflowRequest.java
@@ -53,6 +53,11 @@ public final class ShadowDefaultAlgorithmWorkflowRequest extends WorkflowRequest
}
}
+ @Override
+ public Map<String, String> getAlgorithmProperties(final String algorithmRole) {
+ return "primary".equals(algorithmRole) ? algorithmProperties : super.getAlgorithmProperties(algorithmRole);
+ }
+
@Override
public ShadowDefaultAlgorithmWorkflowRequest copy() {
ShadowDefaultAlgorithmWorkflowRequest result = copyTo(new ShadowDefaultAlgorithmWorkflowRequest());
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequest.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequest.java
index 77445aca4f3..a93be9c62af 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequest.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/model/ShadowRuleWorkflowRequest.java
@@ -98,6 +98,11 @@ public final class ShadowRuleWorkflowRequest extends WorkflowRequest {
}
}
+ @Override
+ public Map<String, String> getAlgorithmProperties(final String algorithmRole) {
+ return "primary".equals(algorithmRole) ? algorithmProperties : super.getAlgorithmProperties(algorithmRole);
+ }
+
@Override
public ShadowRuleWorkflowRequest copy() {
ShadowRuleWorkflowRequest result = copyTo(new ShadowRuleWorkflowRequest());
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmPropertyTemplateService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmPropertyTemplateService.java
new file mode 100644
index 00000000000..3e165fc5b30
--- /dev/null
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmPropertyTemplateService.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.shadow.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Shadow algorithm property template service.
+ */
+public final class ShadowAlgorithmPropertyTemplateService {
+
+ private static final Map<String, List<AlgorithmPropertyRequirement>> TEMPLATES = createTemplates();
+
+ /**
+ * Find property requirements.
+ *
+ * @param algorithmType shadow algorithm type
+ * @return property requirements
+ */
+ public List<AlgorithmPropertyRequirement> findRequirements(final String algorithmType) {
+ return TEMPLATES.getOrDefault(Objects.toString(algorithmType, "").trim().toUpperCase(Locale.ENGLISH), List.of());
+ }
+
+ private static Map<String, List<AlgorithmPropertyRequirement>> createTemplates() {
+ Map<String, List<AlgorithmPropertyRequirement>> result = new LinkedHashMap<>(3, 1F);
+ result.put("SQL_HINT", List.of());
+ result.put("VALUE_MATCH", List.of(
+ new AlgorithmPropertyRequirement("primary", "operation", true, false, "Match operation.", ""),
+ new AlgorithmPropertyRequirement("primary", "column", true, false, "Column used by the shadow match.", ""),
+ new AlgorithmPropertyRequirement("primary", "value", true, false, "Literal value used by the shadow match.", "")));
+ result.put("REGEX_MATCH", List.of(
+ new AlgorithmPropertyRequirement("primary", "operation", true, false, "Match operation.", ""),
+ new AlgorithmPropertyRequirement("primary", "column", true, false, "Column used by the shadow match.", ""),
+ new AlgorithmPropertyRequirement("primary", "regex", true, false, "Regular expression used by the shadow match.", "")));
+ return result;
+ }
+}
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationService.java
new file mode 100644
index 00000000000..bb6d3f09f94
--- /dev/null
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationService.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.shadow.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+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 java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Shadow algorithm recommendation service.
+ */
+public final class ShadowAlgorithmRecommendationService {
+
+ /**
+ * Recommend shadow algorithm.
+ *
+ * @param request workflow request
+ * @param algorithmRows algorithm rows
+ * @param issues workflow issues
+ * @return selected candidates
+ */
+ public List<AlgorithmCandidate> recommendShadowAlgorithms(final WorkflowRequest request, final List<Map<String, Object>> algorithmRows,
+ final List<WorkflowIssue> issues) {
+ List<Map<String, Object>> actualAlgorithmRows = null == algorithmRows ? List.of() : algorithmRows;
+ String actualAlgorithmType = request.getAlgorithmType().toUpperCase(Locale.ENGLISH);
+ if (!actualAlgorithmType.isEmpty()) {
+ if (actualAlgorithmRows.isEmpty() || containsAlgorithm(actualAlgorithmRows, actualAlgorithmType)) {
+ return List.of(createCandidate(actualAlgorithmType, 100, "User specified shadow algorithm."));
+ }
+ issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+ String.format("Shadow algorithm `%s` is not visible from the current Proxy.", actualAlgorithmType),
+ "Choose an available shadow algorithm.", false, Map.of()));
+ return List.of();
+ }
+ String recommendedType = resolveRecommendedAlgorithm(actualAlgorithmRows);
+ if (recommendedType.isEmpty()) {
+ issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+ "No shadow algorithm is available from the current Proxy.", "Install or expose at least one shadow algorithm.", false, Map.of()));
+ return List.of();
+ }
+ return List.of(createCandidate(recommendedType, 90, "Recommended from current shadow algorithm availability."));
+ }
+
+ private AlgorithmCandidate createCandidate(final String algorithmType, final int score, final String reason) {
+ return new AlgorithmCandidate("primary", algorithmType, null, null, null, score, reason, "");
+ }
+
+ private String resolveRecommendedAlgorithm(final List<Map<String, Object>> algorithmRows) {
+ for (String each : List.of("SQL_HINT", "VALUE_MATCH", "REGEX_MATCH")) {
+ if (containsAlgorithm(algorithmRows, each)) {
+ return each;
+ }
+ }
+ return algorithmRows.isEmpty() ? "" : getAlgorithmType(algorithmRows.getFirst());
+ }
+
+ private boolean containsAlgorithm(final List<Map<String, Object>> algorithmRows, final String algorithmType) {
+ return algorithmRows.stream().map(this::getAlgorithmType).anyMatch(algorithmType::equals);
+ }
+
+ private String getAlgorithmType(final Map<String, Object> algorithmRow) {
+ return Objects.toString(algorithmRow.getOrDefault("type", algorithmRow.getOrDefault("name", "")), "").trim().toUpperCase(Locale.ENGLISH);
+ }
+}
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionService.java
index e21238175c2..84ef96c8846 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowInspectionService.java
@@ -125,7 +125,8 @@ public final class ShadowInspectionService {
private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
try {
- return queryFacade.queryWithAnyDatabase("SHOW SHADOW ALGORITHM PLUGINS");
+ List<Map<String, Object>> result = queryFacade.queryWithAnyDatabase("SHOW SHADOW ALGORITHM PLUGINS");
+ return null == result ? List.of(Map.of("type", "SQL_HINT"), Map.of("type", "REGEX_MATCH"), Map.of("type", "VALUE_MATCH")) : result;
} catch (final MCPQueryFailedException ex) {
if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
return List.of(Map.of("type", "SQL_HINT"), Map.of("type", "REGEX_MATCH"), Map.of("type", "VALUE_MATCH"));
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
index 04febb95a4b..35f8d3b5077 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
@@ -23,6 +23,8 @@ import org.apache.shardingsphere.mcp.feature.shadow.tool.model.ShadowDefaultAlgo
import org.apache.shardingsphere.mcp.feature.shadow.tool.model.ShadowRuleWorkflowRequest;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
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.WorkflowIssue;
@@ -38,7 +40,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
/**
@@ -76,15 +77,23 @@ public final class ShadowWorkflowPlanningService {
private final ShadowInspectionService inspectionService;
+ private final ShadowAlgorithmRecommendationService algorithmRecommendationService;
+
+ private final ShadowAlgorithmPropertyTemplateService algorithmPropertyTemplateService;
+
private final ShadowDistSQLPlanningService distSQLPlanningService;
public ShadowWorkflowPlanningService() {
inspectionService = new ShadowInspectionService();
+ algorithmRecommendationService = new ShadowAlgorithmRecommendationService();
+ algorithmPropertyTemplateService = new ShadowAlgorithmPropertyTemplateService();
distSQLPlanningService = new ShadowDistSQLPlanningService();
}
ShadowWorkflowPlanningService(final ShadowInspectionService inspectionService, final ShadowDistSQLPlanningService distSQLPlanningService) {
this.inspectionService = inspectionService;
+ algorithmRecommendationService = new ShadowAlgorithmRecommendationService();
+ algorithmPropertyTemplateService = new ShadowAlgorithmPropertyTemplateService();
this.distSQLPlanningService = distSQLPlanningService;
}
@@ -103,9 +112,17 @@ public final class ShadowWorkflowPlanningService {
ShadowRuleWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.RULE_WORKFLOW_KIND,
resolveIntent(request, "create"), "Shadow rule workflow plan.", RULE_INTERACTION_STEPS);
planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
+ if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType()) && !mergedRequest.getDatabase().isEmpty()) {
+ planAlgorithms(queryFacade, mergedRequest, result);
+ }
if (!ensureRulePlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
}
+ if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType())
+ && !planningSupport.isReadyForArtifactPlanning(mergedRequest, result.getClarifiedIntent(), result, findPropertyRequirements(mergedRequest),
+ "Please use a shadow algorithm visible in the current Proxy and provide required properties.")) {
+ return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
+ }
String databaseType = queryFacade.getDatabaseType(mergedRequest.getDatabase());
if (!ensureRuleLifecycle(result.getClarifiedIntent(), mergedRequest, inspectionService.queryRules(queryFacade, mergedRequest.getDatabase()), result, databaseType)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
@@ -129,9 +146,17 @@ public final class ShadowWorkflowPlanningService {
ShadowDefaultAlgorithmWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.DEFAULT_ALGORITHM_WORKFLOW_KIND,
resolveIntent(request, "create"), "Default shadow algorithm workflow plan.", DEFAULT_ALGORITHM_INTERACTION_STEPS);
planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
+ if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType()) && !mergedRequest.getDatabase().isEmpty()) {
+ planAlgorithms(queryFacade, mergedRequest, result);
+ }
if (!ensureDefaultAlgorithmPlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
}
+ if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType())
+ && !planningSupport.isReadyForArtifactPlanning(mergedRequest, result.getClarifiedIntent(), result, findPropertyRequirements(mergedRequest),
+ "Please use a shadow algorithm visible in the current Proxy and provide required properties.")) {
+ return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
+ }
boolean exists = !inspectionService.queryDefaultAlgorithm(queryFacade, mergedRequest.getDatabase()).isEmpty();
if (!planningSupport.ensureLifecycleState("Default shadow algorithm", result.getClarifiedIntent(), exists, result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
@@ -209,7 +234,6 @@ public final class ShadowWorkflowPlanningService {
addMissingInput(missingInputs, request.getShadowStorageUnit(), ShadowFeatureDefinition.SHADOW_STORAGE_UNIT_FIELD);
addMissingInput(missingInputs, request.getTableName(), ShadowFeatureDefinition.TABLE_FIELD);
addMissingInput(missingInputs, request.getAlgorithmType(), ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD);
- addMissingAlgorithmProperties(missingInputs, request.getAlgorithmType(), request.getAlgorithmProperties());
}
return ensureNoMissingInputs(missingInputs, clarifiedIntent, snapshot, "Shadow rule DistSQL requires explicit rule, storage unit, table and algorithm inputs.");
}
@@ -221,7 +245,6 @@ public final class ShadowWorkflowPlanningService {
List<String> missingInputs = new LinkedList<>();
if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType())) {
addMissingInput(missingInputs, request.getAlgorithmType(), ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD);
- addMissingAlgorithmProperties(missingInputs, request.getAlgorithmType(), request.getAlgorithmProperties());
}
return ensureNoMissingInputs(missingInputs, clarifiedIntent, snapshot, "Default shadow algorithm DistSQL requires an explicit algorithm type and properties.");
}
@@ -260,19 +283,6 @@ public final class ShadowWorkflowPlanningService {
return true;
}
- private void addMissingAlgorithmProperties(final Collection<String> missingInputs, final String algorithmType, final Map<String, String> properties) {
- String actualType = algorithmType.toUpperCase(Locale.ENGLISH);
- if ("VALUE_MATCH".equals(actualType)) {
- addMissingInput(missingInputs, properties.get("operation"), "algorithm_properties.operation");
- addMissingInput(missingInputs, properties.get("column"), "algorithm_properties.column");
- addMissingInput(missingInputs, properties.get("value"), "algorithm_properties.value");
- } else if ("REGEX_MATCH".equals(actualType)) {
- addMissingInput(missingInputs, properties.get("operation"), "algorithm_properties.operation");
- addMissingInput(missingInputs, properties.get("column"), "algorithm_properties.column");
- addMissingInput(missingInputs, properties.get("regex"), "algorithm_properties.regex");
- }
- }
-
private void addMissingInput(final Collection<String> missingInputs, final String value, final String fieldName) {
if (null == value || value.trim().isEmpty()) {
missingInputs.add(fieldName);
@@ -328,6 +338,19 @@ public final class ShadowWorkflowPlanningService {
WorkflowRuleValueUtils.getRuleValue(each, "shadow_algorithm_name")));
}
+ private void planAlgorithms(final MCPFeatureQueryFacade queryFacade, final WorkflowRequest request, final WorkflowContextSnapshot snapshot) {
+ List<AlgorithmCandidate> algorithmCandidates = algorithmRecommendationService.recommendShadowAlgorithms(
+ request, inspectionService.queryAlgorithmPlugins(queryFacade), snapshot.getIssues());
+ snapshot.getAlgorithmCandidates().addAll(algorithmCandidates);
+ if (!algorithmCandidates.isEmpty()) {
+ request.setAlgorithmType(algorithmCandidates.getFirst().getAlgorithmType());
+ }
+ }
+
+ private List<AlgorithmPropertyRequirement> findPropertyRequirements(final WorkflowRequest request) {
+ return algorithmPropertyTemplateService.findRequirements(request.getAlgorithmType());
+ }
+
private void addRuleArtifact(final WorkflowContextSnapshot snapshot, final ShadowRuleWorkflowRequest request, final String operationType) {
if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(operationType)) {
snapshot.getRuleArtifacts().add(distSQLPlanningService.planDropRule(request.getRuleName()));
diff --git a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml
index 76261206bea..7504413a5d2 100644
--- a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml
+++ b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-shadow.yaml
@@ -197,6 +197,10 @@ prompts:
title: Algorithm Type
description: "Shadow algorithm type, such as SQL_HINT, VALUE_MATCH, or REGEX_MATCH."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a shadow rule plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_shadow_rule
@@ -230,6 +234,10 @@ prompts:
title: Algorithm Type
description: "Default shadow algorithm type."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a default shadow algorithm plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_default_shadow_algorithm
@@ -258,6 +266,10 @@ prompts:
title: Algorithm Name
description: "Configured shadow algorithm name to clean up."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a shadow algorithm cleanup plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_shadow_algorithm_cleanup
@@ -316,8 +328,15 @@ resourceNavigation:
- from: shardingsphere://features/shadow/algorithm-plugins
to: database_gateway_plan_shadow_rule
requiredArguments: []
- carriedArguments: []
+ carriedArguments:
+ - algorithm_type
description: "Choose an available shadow algorithm before planning create or alter."
+ - from: shardingsphere://features/shadow/algorithm-plugins
+ to: database_gateway_plan_default_shadow_algorithm
+ requiredArguments: []
+ carriedArguments:
+ - algorithm_type
+ description: "Choose an available shadow algorithm before planning the default algorithm."
- from: database_gateway_plan_shadow_rule
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -325,6 +344,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning shadow rule changes."
+ - from: database_gateway_plan_shadow_rule
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed shadow rule workflow after user approval."
- from: database_gateway_plan_default_shadow_algorithm
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -332,6 +358,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning default shadow algorithm changes."
+ - from: database_gateway_plan_default_shadow_algorithm
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed default shadow algorithm workflow after user approval."
- from: database_gateway_plan_shadow_algorithm_cleanup
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -339,6 +372,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning shadow algorithm cleanup."
+ - from: database_gateway_plan_shadow_algorithm_cleanup
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed shadow algorithm cleanup workflow after user approval."
tools:
- name: database_gateway_plan_shadow_rule
title: Plan Shadow Rule
diff --git a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-default-shadow-algorithm.md b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-default-shadow-algorithm.md
index ebfdf28af9e..cea152b686a 100644
--- a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-default-shadow-algorithm.md
+++ b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-default-shadow-algorithm.md
@@ -2,6 +2,12 @@
Read the current default shadow algorithm, configured shadow algorithms, and shadow algorithm plugins before planning.
+Inputs:
+- database: {{database}}
+- operation_type: {{operation_type}}
+- algorithm_type: {{algorithm_type}}
+- plan_id: {{plan_id}}
+
Generate only `CREATE DEFAULT SHADOW ALGORITHM`, `ALTER DEFAULT SHADOW ALGORITHM`, or `DROP DEFAULT SHADOW ALGORITHM` DistSQL artifacts. Current ShardingSphere grammar does not include a `FROM` clause for default shadow algorithm RDL, so use the logical database as workflow context and not as a rendered RDL suffix.
Ask the user when the logical database, operation, algorithm type, or required properties are unclear.
diff --git a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-algorithm-cleanup.md b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-algorithm-cleanup.md
index 3a46ddb9758..d1f31b48bb0 100644
--- a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-algorithm-cleanup.md
+++ b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-algorithm-cleanup.md
@@ -2,6 +2,11 @@
Read configured shadow algorithms, shadow table rules, default shadow algorithm, and shadow rules before cleanup planning.
+Inputs:
+- database: {{database}}
+- algorithm_name: {{algorithm_name}}
+- plan_id: {{plan_id}}
+
Generate `DROP SHADOW ALGORITHM` only when the target algorithm is configured and is not referenced by table rules or the default shadow algorithm. Do not remove shadow rules, storage units, physical databases, physical tables, indexes, migration jobs, backfill jobs, or data probes.
Ask the user when the logical database or algorithm name is unclear, or when the algorithm is still referenced.
diff --git a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-rule.md b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-rule.md
index 12bb343e792..967808c8774 100644
--- a/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-rule.md
+++ b/mcp/features/shadow/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-shadow-rule.md
@@ -2,6 +2,16 @@
Read the current shadow rules, table rules, configured algorithms, default algorithm, and shadow algorithm plugins before planning.
+Inputs:
+- database: {{database}}
+- rule: {{rule}}
+- operation_type: {{operation_type}}
+- source_storage_unit: {{source_storage_unit}}
+- shadow_storage_unit: {{shadow_storage_unit}}
+- table: {{table}}
+- algorithm_type: {{algorithm_type}}
+- plan_id: {{plan_id}}
+
Generate only `CREATE SHADOW RULE`, `ALTER SHADOW RULE`, or `DROP SHADOW RULE` DistSQL artifacts. Do not create storage units, physical databases, physical tables, indexes, migration jobs, backfill jobs, or data probes.
Ask the user when the logical database, rule name, source storage unit, shadow storage unit, table, algorithm type, or required algorithm properties are unclear.
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/descriptor/ShadowToolDescriptorValidatorTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/descriptor/ShadowToolDescriptorValidatorTest.java
index 3e3d414ec09..ab1d28165ca 100644
--- a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/descriptor/ShadowToolDescriptorValidatorTest.java
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/descriptor/ShadowToolDescriptorValidatorTest.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.api.tool.descriptor.MCPToolDescriptor;
import org.apache.shardingsphere.mcp.feature.shadow.ShadowFeatureDefinition;
import org.apache.shardingsphere.mcp.support.descriptor.MCPCompletionTargetDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPResourceNavigationDescriptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -96,6 +97,22 @@ class ShadowToolDescriptorValidatorTest {
}
}
+ @Test
+ void assertPlanToolsNavigateToWorkflowAndApply() {
+ for (String each : List.of(ShadowFeatureDefinition.PLAN_RULE_TOOL_NAME, ShadowFeatureDefinition.PLAN_DEFAULT_ALGORITHM_TOOL_NAME,
+ ShadowFeatureDefinition.PLAN_ALGORITHM_CLEANUP_TOOL_NAME)) {
+ assertPlanToolNavigation(each);
+ }
+ }
+
+ @Test
+ void assertAlgorithmResourceNavigationCarriesAlgorithmType() {
+ assertThat(findNavigation(ShadowFeatureDefinition.ALGORITHM_PLUGINS_RESOURCE_URI,
+ ShadowFeatureDefinition.PLAN_RULE_TOOL_NAME).getCarriedArguments(), is(List.of(ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD)));
+ assertThat(findNavigation(ShadowFeatureDefinition.ALGORITHM_PLUGINS_RESOURCE_URI,
+ ShadowFeatureDefinition.PLAN_DEFAULT_ALGORITHM_TOOL_NAME).getCarriedArguments(), is(List.of(ShadowFeatureDefinition.ALGORITHM_TYPE_FIELD)));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("requiredOutputFields")
@SuppressWarnings("unchecked")
@@ -147,4 +164,20 @@ class ShadowToolDescriptorValidatorTest {
return MCPDescriptorCatalogIndex.getCompletionTargetDescriptors().stream()
.anyMatch(each -> referenceType.equals(each.getReferenceType()) && reference.equals(each.getReference()));
}
+
+ private void assertPlanToolNavigation(final String toolName) {
+ assertNavigation(toolName, "shardingsphere://workflows/{plan_id}");
+ assertNavigation(toolName, "database_gateway_apply_workflow");
+ }
+
+ private void assertNavigation(final String from, final String to) {
+ MCPResourceNavigationDescriptor actual = findNavigation(from, to);
+ assertThat(actual.getRequiredArguments(), is(List.of("plan_id")));
+ assertThat(actual.getCarriedArguments(), is(List.of("plan_id")));
+ }
+
+ private MCPResourceNavigationDescriptor findNavigation(final String from, final String to) {
+ return MCPDescriptorCatalogIndex.getResourceNavigationDescriptors(from).stream()
+ .filter(each -> to.equals(each.getTo())).findFirst().orElseThrow();
+ }
}
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmPropertyTemplateServiceTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmPropertyTemplateServiceTest.java
new file mode 100644
index 00000000000..871824a0210
--- /dev/null
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmPropertyTemplateServiceTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.shadow.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ShadowAlgorithmPropertyTemplateServiceTest {
+
+ private final ShadowAlgorithmPropertyTemplateService service = new ShadowAlgorithmPropertyTemplateService();
+
+ @Test
+ void assertFindRequirementsForValueMatch() {
+ List<AlgorithmPropertyRequirement> actual = service.findRequirements("VALUE_MATCH");
+ assertThat(actual.size(), is(3));
+ assertThat(actual.get(0).getPropertyKey(), is("operation"));
+ assertThat(actual.get(1).getPropertyKey(), is("column"));
+ assertThat(actual.get(2).getPropertyKey(), is("value"));
+ }
+
+ @Test
+ void assertFindRequirementsForRegexMatch() {
+ assertThat(service.findRequirements("REGEX_MATCH").get(2).getPropertyKey(), is("regex"));
+ }
+
+ @Test
+ void assertFindRequirementsForSqlHint() {
+ assertTrue(service.findRequirements("SQL_HINT").isEmpty());
+ }
+}
diff --git a/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationServiceTest.java b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationServiceTest.java
new file mode 100644
index 00000000000..12f71f87bac
--- /dev/null
+++ b/mcp/features/shadow/src/test/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowAlgorithmRecommendationServiceTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.shadow.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+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.junit.jupiter.api.Test;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ShadowAlgorithmRecommendationServiceTest {
+
+ private final ShadowAlgorithmRecommendationService service = new ShadowAlgorithmRecommendationService();
+
+ @Test
+ void assertRecommendSpecifiedAlgorithm() {
+ WorkflowRequest request = new WorkflowRequest();
+ request.setAlgorithmType("value_match");
+ List<AlgorithmCandidate> actual = service.recommendShadowAlgorithms(request, List.of(Map.of("type", "VALUE_MATCH")), new LinkedList<>());
+ assertThat(actual.getFirst().getAlgorithmType(), is("VALUE_MATCH"));
+ }
+
+ @Test
+ void assertRecommendSqlHintDefault() {
+ WorkflowRequest request = new WorkflowRequest();
+ List<AlgorithmCandidate> actual = service.recommendShadowAlgorithms(request, List.of(Map.of("type", "VALUE_MATCH"), Map.of("type", "SQL_HINT")), new LinkedList<>());
+ assertThat(actual.getFirst().getAlgorithmType(), is("SQL_HINT"));
+ }
+
+ @Test
+ void assertRecommendRejectsInvisibleAlgorithm() {
+ WorkflowRequest request = new WorkflowRequest();
+ request.setAlgorithmType("REGEX_MATCH");
+ List<WorkflowIssue> issues = new LinkedList<>();
+ assertTrue(service.recommendShadowAlgorithms(request, List.of(Map.of("type", "SQL_HINT")), issues).isEmpty());
+ assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
+ }
+}
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 2b03028d6fa..77422162131 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
@@ -23,6 +23,7 @@ import org.apache.shardingsphere.mcp.feature.shadow.tool.model.ShadowDefaultAlgo
import org.apache.shardingsphere.mcp.feature.shadow.tool.model.ShadowRuleWorkflowRequest;
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.junit.jupiter.api.Test;
@@ -45,7 +46,9 @@ class ShadowWorkflowPlanningServiceTest {
WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService(inspectionService, new ShadowDistSQLPlanningService())
.planRule(new TestWorkflowSessionContext(), queryFacade, "session-1", createRuleRequest());
assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_PLANNED));
- assertTrue(actual.getRuleArtifacts().get(0).getSql().startsWith("CREATE SHADOW RULE shadow_rule"));
+ assertThat(actual.getAlgorithmCandidates().getFirst().getAlgorithmType(), is("VALUE_MATCH"));
+ assertThat(actual.getPropertyRequirements().getFirst().getPropertyKey(), is("operation"));
+ assertTrue(actual.getRuleArtifacts().getFirst().getSql().startsWith("CREATE SHADOW RULE shadow_rule"));
}
@Test
@@ -55,9 +58,26 @@ class ShadowWorkflowPlanningServiceTest {
WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService()
.planRule(new TestWorkflowSessionContext(), mock(MCPFeatureQueryFacade.class), "session-1", request);
assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_CLARIFYING));
+ assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.REQUIRED_PROPERTY_MISSING));
assertTrue(actual.getRuleArtifacts().isEmpty());
}
+ @Test
+ void assertPlanRuleWithDefaultSqlHintRecommendation() {
+ ShadowInspectionService inspectionService = mock(ShadowInspectionService.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ ShadowRuleWorkflowRequest request = createRuleRequest();
+ request.setAlgorithmType("");
+ request.getAlgorithmProperties().clear();
+ when(inspectionService.queryAlgorithmPlugins(queryFacade)).thenReturn(List.of(Map.of("type", "SQL_HINT")));
+ when(inspectionService.queryRules(queryFacade, "logic_db")).thenReturn(List.of());
+ WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService(inspectionService, new ShadowDistSQLPlanningService())
+ .planRule(new TestWorkflowSessionContext(), queryFacade, "session-1", request);
+ assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_PLANNED));
+ assertThat(actual.getAlgorithmCandidates().getFirst().getAlgorithmType(), is("SQL_HINT"));
+ assertTrue(actual.getPropertyRequirements().isEmpty());
+ }
+
@Test
void assertPlanDefaultAlgorithmDrop() {
ShadowInspectionService inspectionService = mock(ShadowInspectionService.class);
@@ -68,7 +88,7 @@ class ShadowWorkflowPlanningServiceTest {
when(inspectionService.queryDefaultAlgorithm(queryFacade, "logic_db")).thenReturn(List.of(Map.of("shadow_algorithm_name", "default_shadow_algorithm")));
WorkflowContextSnapshot actual = new ShadowWorkflowPlanningService(inspectionService, new ShadowDistSQLPlanningService())
.planDefaultAlgorithm(new TestWorkflowSessionContext(), queryFacade, "session-1", request);
- assertThat(actual.getRuleArtifacts().get(0).getSql(), is("DROP DEFAULT SHADOW ALGORITHM"));
+ assertThat(actual.getRuleArtifacts().getFirst().getSql(), is("DROP DEFAULT SHADOW ALGORITHM"));
}
@Test
@@ -81,7 +101,7 @@ class ShadowWorkflowPlanningServiceTest {
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().get(0).getSql(), is("DROP SHADOW ALGORITHM unused_algorithm"));
+ assertThat(actual.getRuleArtifacts().getFirst().getSql(), is("DROP SHADOW ALGORITHM unused_algorithm"));
}
@Test
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProvider.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProvider.java
index d51d6c9a0fa..7c90bd340a8 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProvider.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProvider.java
@@ -20,7 +20,10 @@ package org.apache.shardingsphere.mcp.feature.sharding;
import org.apache.shardingsphere.mcp.api.MCPHandlerProvider;
import org.apache.shardingsphere.mcp.api.resource.MCPResourceHandler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolHandler;
-import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingResourceHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingAlgorithmResourceHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingGovernanceResourceHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingStrategyResourceHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingTableResourceHandler;
import org.apache.shardingsphere.mcp.feature.sharding.tool.handler.PlanShardingDefaultStrategyToolHandler;
import org.apache.shardingsphere.mcp.feature.sharding.tool.handler.PlanShardingKeyGenerateStrategyToolHandler;
import org.apache.shardingsphere.mcp.feature.sharding.tool.handler.PlanShardingKeyGeneratorToolHandler;
@@ -42,28 +45,28 @@ public final class ShardingMCPHandlerProvider implements MCPHandlerProvider, MCP
@Override
public Collection<MCPResourceHandler<?>> getResourceHandlers() {
return List.of(
- ShardingResourceHandler.algorithmPlugins(),
- ShardingResourceHandler.keyGenerateAlgorithmPlugins(),
- ShardingResourceHandler.algorithms(),
- ShardingResourceHandler.tableRules(),
- ShardingResourceHandler.tableRule(),
- ShardingResourceHandler.tableNodes(),
- ShardingResourceHandler.tableNode(),
- ShardingResourceHandler.tableReferenceRules(),
- ShardingResourceHandler.tableReferenceRule(),
- ShardingResourceHandler.defaultStrategy(),
- ShardingResourceHandler.keyGenerators(),
- ShardingResourceHandler.keyGenerator(),
- ShardingResourceHandler.keyGenerateStrategies(),
- ShardingResourceHandler.keyGenerateStrategy(),
- ShardingResourceHandler.auditors(),
- ShardingResourceHandler.unusedAlgorithms(),
- ShardingResourceHandler.unusedKeyGenerators(),
- ShardingResourceHandler.unusedAuditors(),
- ShardingResourceHandler.algorithmUsedTableRules(),
- ShardingResourceHandler.keyGeneratorUsedTableRules(),
- ShardingResourceHandler.auditorUsedTableRules(),
- ShardingResourceHandler.ruleCount());
+ ShardingAlgorithmResourceHandler.algorithmPlugins(),
+ ShardingAlgorithmResourceHandler.keyGenerateAlgorithmPlugins(),
+ ShardingAlgorithmResourceHandler.algorithms(),
+ ShardingTableResourceHandler.tableRules(),
+ ShardingTableResourceHandler.tableRule(),
+ ShardingTableResourceHandler.tableNodes(),
+ ShardingTableResourceHandler.tableNode(),
+ ShardingTableResourceHandler.tableReferenceRules(),
+ ShardingTableResourceHandler.tableReferenceRule(),
+ ShardingStrategyResourceHandler.defaultStrategy(),
+ ShardingStrategyResourceHandler.keyGenerators(),
+ ShardingStrategyResourceHandler.keyGenerator(),
+ ShardingStrategyResourceHandler.keyGenerateStrategies(),
+ ShardingStrategyResourceHandler.keyGenerateStrategy(),
+ ShardingGovernanceResourceHandler.auditors(),
+ ShardingAlgorithmResourceHandler.unusedAlgorithms(),
+ ShardingStrategyResourceHandler.unusedKeyGenerators(),
+ ShardingGovernanceResourceHandler.unusedAuditors(),
+ ShardingAlgorithmResourceHandler.algorithmUsedTableRules(),
+ ShardingStrategyResourceHandler.keyGeneratorUsedTableRules(),
+ ShardingGovernanceResourceHandler.auditorUsedTableRules(),
+ ShardingGovernanceResourceHandler.ruleCount());
}
@Override
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/AbstractShardingResourceHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/AbstractShardingResourceHandler.java
new file mode 100644
index 00000000000..6315b2ba9d3
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/AbstractShardingResourceHandler.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.resource.MCPResourceHandler;
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPResourceNavigationPayloadBuilder;
+import org.apache.shardingsphere.mcp.support.protocol.response.MCPItemsResponse;
+
+import java.util.List;
+import java.util.Map;
+
+abstract class AbstractShardingResourceHandler implements MCPResourceHandler<MCPDatabaseHandlerContext> {
+
+ private final String resourceUriTemplate;
+
+ private final ShardingInspectionService inspectionService;
+
+ AbstractShardingResourceHandler(final String resourceUriTemplate, final ShardingInspectionService inspectionService) {
+ this.resourceUriTemplate = resourceUriTemplate;
+ this.inspectionService = inspectionService;
+ }
+
+ @Override
+ public Class<MCPDatabaseHandlerContext> getContextType() {
+ return MCPDatabaseHandlerContext.class;
+ }
+
+ @Override
+ public String getResourceUriTemplate() {
+ return resourceUriTemplate;
+ }
+
+ @Override
+ public MCPResponse handle(final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
+ return new MCPItemsResponse(query(databaseContext, uriVariables),
+ MCPResourceNavigationPayloadBuilder.create(MCPDescriptorCatalogIndex.getRequiredResourceDescriptor(getResourceUriTemplate()), uriVariables));
+ }
+
+ protected ShardingInspectionService getInspectionService() {
+ return inspectionService;
+ }
+
+ protected abstract List<Map<String, Object>> query(MCPDatabaseHandlerContext databaseContext, MCPUriVariables uriVariables);
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingAlgorithmResourceHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingAlgorithmResourceHandler.java
new file mode 100644
index 00000000000..8a7d9121b71
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingAlgorithmResourceHandler.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Sharding algorithm resource handler.
+ */
+public final class ShardingAlgorithmResourceHandler extends AbstractShardingResourceHandler {
+
+ private final ResourceKind resourceKind;
+
+ private ShardingAlgorithmResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind) {
+ this(resourceUriTemplate, resourceKind, new ShardingInspectionService());
+ }
+
+ ShardingAlgorithmResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind, final ShardingInspectionService inspectionService) {
+ super(resourceUriTemplate, inspectionService);
+ this.resourceKind = resourceKind;
+ }
+
+ /**
+ * Create handler for sharding algorithm plugin resources.
+ *
+ * @return sharding algorithm resource handler
+ */
+ public static ShardingAlgorithmResourceHandler algorithmPlugins() {
+ return new ShardingAlgorithmResourceHandler(ShardingFeatureDefinition.ALGORITHM_PLUGINS_RESOURCE_URI, ResourceKind.ALGORITHM_PLUGINS);
+ }
+
+ /**
+ * Create handler for key generate algorithm plugin resources.
+ *
+ * @return sharding algorithm resource handler
+ */
+ public static ShardingAlgorithmResourceHandler keyGenerateAlgorithmPlugins() {
+ return new ShardingAlgorithmResourceHandler(ShardingFeatureDefinition.KEY_GENERATE_ALGORITHM_PLUGINS_RESOURCE_URI, ResourceKind.KEY_GENERATE_ALGORITHM_PLUGINS);
+ }
+
+ /**
+ * Create handler for sharding algorithm resources.
+ *
+ * @return sharding algorithm resource handler
+ */
+ public static ShardingAlgorithmResourceHandler algorithms() {
+ return new ShardingAlgorithmResourceHandler(ShardingFeatureDefinition.ALGORITHMS_RESOURCE_URI, ResourceKind.ALGORITHMS);
+ }
+
+ /**
+ * Create handler for unused sharding algorithm resources.
+ *
+ * @return sharding algorithm resource handler
+ */
+ public static ShardingAlgorithmResourceHandler unusedAlgorithms() {
+ return new ShardingAlgorithmResourceHandler(ShardingFeatureDefinition.UNUSED_ALGORITHMS_RESOURCE_URI, ResourceKind.UNUSED_ALGORITHMS);
+ }
+
+ /**
+ * Create handler for table rules that use a sharding algorithm.
+ *
+ * @return sharding algorithm resource handler
+ */
+ public static ShardingAlgorithmResourceHandler algorithmUsedTableRules() {
+ return new ShardingAlgorithmResourceHandler(ShardingFeatureDefinition.ALGORITHM_USED_TABLE_RULES_RESOURCE_URI, ResourceKind.ALGORITHM_USED_TABLE_RULES);
+ }
+
+ @Override
+ protected List<Map<String, Object>> query(final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
+ return switch (resourceKind) {
+ case ALGORITHM_PLUGINS -> getInspectionService().queryAlgorithmPlugins(databaseContext.getQueryFacade());
+ case KEY_GENERATE_ALGORITHM_PLUGINS -> getInspectionService().queryKeyGenerateAlgorithmPlugins(databaseContext.getQueryFacade());
+ case ALGORITHMS -> getInspectionService().queryAlgorithms(databaseContext.getQueryFacade(), uriVariables.getValue("database"));
+ case UNUSED_ALGORITHMS -> getInspectionService().queryUnusedAlgorithms(databaseContext.getQueryFacade(), uriVariables.getValue("database"));
+ case ALGORITHM_USED_TABLE_RULES -> getInspectionService().queryTableRulesUsedAlgorithm(
+ databaseContext.getQueryFacade(), uriVariables.getValue("database"), uriVariables.getValue(ShardingFeatureDefinition.ALGORITHM_FIELD));
+ };
+ }
+
+ enum ResourceKind {
+
+ ALGORITHM_PLUGINS, KEY_GENERATE_ALGORITHM_PLUGINS, ALGORITHMS, UNUSED_ALGORITHMS, ALGORITHM_USED_TABLE_RULES
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingGovernanceResourceHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingGovernanceResourceHandler.java
new file mode 100644
index 00000000000..ff6d5ffdd34
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingGovernanceResourceHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Sharding governance resource handler.
+ */
+public final class ShardingGovernanceResourceHandler extends AbstractShardingResourceHandler {
+
+ private final ResourceKind resourceKind;
+
+ private ShardingGovernanceResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind) {
+ this(resourceUriTemplate, resourceKind, new ShardingInspectionService());
+ }
+
+ ShardingGovernanceResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind, final ShardingInspectionService inspectionService) {
+ super(resourceUriTemplate, inspectionService);
+ this.resourceKind = resourceKind;
+ }
+
+ /**
+ * Create handler for sharding auditor resources.
+ *
+ * @return sharding governance resource handler
+ */
+ public static ShardingGovernanceResourceHandler auditors() {
+ return new ShardingGovernanceResourceHandler(ShardingFeatureDefinition.AUDITORS_RESOURCE_URI, ResourceKind.AUDITORS);
+ }
+
+ /**
+ * Create handler for unused sharding auditor resources.
+ *
+ * @return sharding governance resource handler
+ */
+ public static ShardingGovernanceResourceHandler unusedAuditors() {
+ return new ShardingGovernanceResourceHandler(ShardingFeatureDefinition.UNUSED_AUDITORS_RESOURCE_URI, ResourceKind.UNUSED_AUDITORS);
+ }
+
+ /**
+ * Create handler for table rules that use a sharding auditor.
+ *
+ * @return sharding governance resource handler
+ */
+ public static ShardingGovernanceResourceHandler auditorUsedTableRules() {
+ return new ShardingGovernanceResourceHandler(ShardingFeatureDefinition.AUDITOR_USED_TABLE_RULES_RESOURCE_URI, ResourceKind.AUDITOR_USED_TABLE_RULES);
+ }
+
+ /**
+ * Create handler for sharding rule count resources.
+ *
+ * @return sharding governance resource handler
+ */
+ public static ShardingGovernanceResourceHandler ruleCount() {
+ return new ShardingGovernanceResourceHandler(ShardingFeatureDefinition.RULE_COUNT_RESOURCE_URI, ResourceKind.RULE_COUNT);
+ }
+
+ @Override
+ protected List<Map<String, Object>> query(final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
+ String databaseName = uriVariables.getValue("database");
+ return switch (resourceKind) {
+ case AUDITORS -> getInspectionService().queryAuditors(databaseContext.getQueryFacade(), databaseName);
+ case UNUSED_AUDITORS -> getInspectionService().queryUnusedAuditors(databaseContext.getQueryFacade(), databaseName);
+ case AUDITOR_USED_TABLE_RULES -> getInspectionService().queryTableRulesUsedAuditor(
+ databaseContext.getQueryFacade(), databaseName, uriVariables.getValue(ShardingFeatureDefinition.AUDITOR_FIELD));
+ case RULE_COUNT -> getInspectionService().queryRuleCount(databaseContext.getQueryFacade(), databaseName);
+ };
+ }
+
+ enum ResourceKind {
+
+ AUDITORS, UNUSED_AUDITORS, AUDITOR_USED_TABLE_RULES, RULE_COUNT
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingStrategyResourceHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingStrategyResourceHandler.java
new file mode 100644
index 00000000000..913adf8eb5d
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingStrategyResourceHandler.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Sharding strategy resource handler.
+ */
+public final class ShardingStrategyResourceHandler extends AbstractShardingResourceHandler {
+
+ private final ResourceKind resourceKind;
+
+ private ShardingStrategyResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind) {
+ this(resourceUriTemplate, resourceKind, new ShardingInspectionService());
+ }
+
+ ShardingStrategyResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind, final ShardingInspectionService inspectionService) {
+ super(resourceUriTemplate, inspectionService);
+ this.resourceKind = resourceKind;
+ }
+
+ /**
+ * Create handler for default sharding strategy resources.
+ *
+ * @return sharding strategy resource handler
+ */
+ public static ShardingStrategyResourceHandler defaultStrategy() {
+ return new ShardingStrategyResourceHandler(ShardingFeatureDefinition.DEFAULT_STRATEGY_RESOURCE_URI, ResourceKind.DEFAULT_STRATEGY);
+ }
+
+ /**
+ * Create handler for sharding key generator resources.
+ *
+ * @return sharding strategy resource handler
+ */
+ public static ShardingStrategyResourceHandler keyGenerators() {
+ return new ShardingStrategyResourceHandler(ShardingFeatureDefinition.KEY_GENERATORS_RESOURCE_URI, ResourceKind.KEY_GENERATORS);
+ }
+
+ /**
+ * Create handler for a sharding key generator resource.
+ *
+ * @return sharding strategy resource handler
+ */
+ public static ShardingStrategyResourceHandler keyGenerator() {
+ return new ShardingStrategyResourceHandler(ShardingFeatureDefinition.KEY_GENERATOR_RESOURCE_URI, ResourceKind.KEY_GENERATOR);
+ }
+
+ /**
+ * Create handler for sharding key generate strategy resources.
+ *
+ * @return sharding strategy resource handler
+ */
+ public static ShardingStrategyResourceHandler keyGenerateStrategies() {
+ return new ShardingStrategyResourceHandler(ShardingFeatureDefinition.KEY_GENERATE_STRATEGIES_RESOURCE_URI, ResourceKind.KEY_GENERATE_STRATEGIES);
+ }
+
+ /**
+ * Create handler for a sharding key generate strategy resource.
+ *
+ * @return sharding strategy resource handler
+ */
+ public static ShardingStrategyResourceHandler keyGenerateStrategy() {
+ return new ShardingStrategyResourceHandler(ShardingFeatureDefinition.KEY_GENERATE_STRATEGY_RESOURCE_URI, ResourceKind.KEY_GENERATE_STRATEGY);
+ }
+
+ /**
+ * Create handler for unused sharding key generator resources.
+ *
+ * @return sharding strategy resource handler
+ */
+ public static ShardingStrategyResourceHandler unusedKeyGenerators() {
+ return new ShardingStrategyResourceHandler(ShardingFeatureDefinition.UNUSED_KEY_GENERATORS_RESOURCE_URI, ResourceKind.UNUSED_KEY_GENERATORS);
+ }
+
+ /**
+ * Create handler for table rules that use a sharding key generator.
+ *
+ * @return sharding strategy resource handler
+ */
+ public static ShardingStrategyResourceHandler keyGeneratorUsedTableRules() {
+ return new ShardingStrategyResourceHandler(ShardingFeatureDefinition.KEY_GENERATOR_USED_TABLE_RULES_RESOURCE_URI, ResourceKind.KEY_GENERATOR_USED_TABLE_RULES);
+ }
+
+ @Override
+ protected List<Map<String, Object>> query(final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
+ String databaseName = uriVariables.getValue("database");
+ return switch (resourceKind) {
+ case DEFAULT_STRATEGY -> getInspectionService().queryDefaultStrategy(databaseContext.getQueryFacade(), databaseName);
+ case KEY_GENERATORS -> getInspectionService().queryKeyGenerators(databaseContext.getQueryFacade(), databaseName);
+ case KEY_GENERATOR -> getInspectionService().queryKeyGenerator(databaseContext.getQueryFacade(), databaseName, uriVariables.getValue(ShardingFeatureDefinition.KEY_GENERATOR_FIELD));
+ case KEY_GENERATE_STRATEGIES -> getInspectionService().queryKeyGenerateStrategies(databaseContext.getQueryFacade(), databaseName);
+ case KEY_GENERATE_STRATEGY -> getInspectionService().queryKeyGenerateStrategy(
+ databaseContext.getQueryFacade(), databaseName, uriVariables.getValue(ShardingFeatureDefinition.STRATEGY_FIELD));
+ case UNUSED_KEY_GENERATORS -> getInspectionService().queryUnusedKeyGenerators(databaseContext.getQueryFacade(), databaseName);
+ case KEY_GENERATOR_USED_TABLE_RULES -> getInspectionService().queryTableRulesUsedKeyGenerator(
+ databaseContext.getQueryFacade(), databaseName, uriVariables.getValue(ShardingFeatureDefinition.KEY_GENERATOR_FIELD));
+ };
+ }
+
+ enum ResourceKind {
+
+ DEFAULT_STRATEGY, KEY_GENERATORS, KEY_GENERATOR, KEY_GENERATE_STRATEGIES, KEY_GENERATE_STRATEGY, UNUSED_KEY_GENERATORS, KEY_GENERATOR_USED_TABLE_RULES
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandler.java
new file mode 100644
index 00000000000..43024669b12
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Sharding table resource handler.
+ */
+public final class ShardingTableResourceHandler extends AbstractShardingResourceHandler {
+
+ private final ResourceKind resourceKind;
+
+ private ShardingTableResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind) {
+ this(resourceUriTemplate, resourceKind, new ShardingInspectionService());
+ }
+
+ ShardingTableResourceHandler(final String resourceUriTemplate, final ResourceKind resourceKind, final ShardingInspectionService inspectionService) {
+ super(resourceUriTemplate, inspectionService);
+ this.resourceKind = resourceKind;
+ }
+
+ /**
+ * Create handler for sharding table rule resources.
+ *
+ * @return sharding table resource handler
+ */
+ public static ShardingTableResourceHandler tableRules() {
+ return new ShardingTableResourceHandler(ShardingFeatureDefinition.TABLE_RULES_RESOURCE_URI, ResourceKind.TABLE_RULES);
+ }
+
+ /**
+ * Create handler for a sharding table rule resource.
+ *
+ * @return sharding table resource handler
+ */
+ public static ShardingTableResourceHandler tableRule() {
+ return new ShardingTableResourceHandler(ShardingFeatureDefinition.TABLE_RULE_RESOURCE_URI, ResourceKind.TABLE_RULE);
+ }
+
+ /**
+ * Create handler for sharding table node resources.
+ *
+ * @return sharding table resource handler
+ */
+ public static ShardingTableResourceHandler tableNodes() {
+ return new ShardingTableResourceHandler(ShardingFeatureDefinition.TABLE_NODES_RESOURCE_URI, ResourceKind.TABLE_NODES);
+ }
+
+ /**
+ * Create handler for a sharding table node resource.
+ *
+ * @return sharding table resource handler
+ */
+ public static ShardingTableResourceHandler tableNode() {
+ return new ShardingTableResourceHandler(ShardingFeatureDefinition.TABLE_NODE_RESOURCE_URI, ResourceKind.TABLE_NODE);
+ }
+
+ /**
+ * Create handler for sharding table reference rule resources.
+ *
+ * @return sharding table resource handler
+ */
+ public static ShardingTableResourceHandler tableReferenceRules() {
+ return new ShardingTableResourceHandler(ShardingFeatureDefinition.TABLE_REFERENCE_RULES_RESOURCE_URI, ResourceKind.TABLE_REFERENCE_RULES);
+ }
+
+ /**
+ * Create handler for a sharding table reference rule resource.
+ *
+ * @return sharding table resource handler
+ */
+ public static ShardingTableResourceHandler tableReferenceRule() {
+ return new ShardingTableResourceHandler(ShardingFeatureDefinition.TABLE_REFERENCE_RULE_RESOURCE_URI, ResourceKind.TABLE_REFERENCE_RULE);
+ }
+
+ @Override
+ protected List<Map<String, Object>> query(final MCPDatabaseHandlerContext databaseContext, final MCPUriVariables uriVariables) {
+ String databaseName = uriVariables.getValue("database");
+ return switch (resourceKind) {
+ case TABLE_RULES -> getInspectionService().queryTableRules(databaseContext.getQueryFacade(), databaseName);
+ case TABLE_RULE -> getInspectionService().queryTableRule(databaseContext.getQueryFacade(), databaseName, uriVariables.getValue(ShardingFeatureDefinition.TABLE_FIELD));
+ case TABLE_NODES -> getInspectionService().queryTableNodes(databaseContext.getQueryFacade(), databaseName);
+ case TABLE_NODE -> getInspectionService().queryTableNode(databaseContext.getQueryFacade(), databaseName, uriVariables.getValue(ShardingFeatureDefinition.TABLE_FIELD));
+ case TABLE_REFERENCE_RULES -> getInspectionService().queryTableReferenceRules(databaseContext.getQueryFacade(), databaseName);
+ case TABLE_REFERENCE_RULE -> getInspectionService().queryTableReferenceRule(databaseContext.getQueryFacade(), databaseName, uriVariables.getValue(ShardingFeatureDefinition.RULE_FIELD));
+ };
+ }
+
+ enum ResourceKind {
+
+ TABLE_RULES, TABLE_RULE, TABLE_NODES, TABLE_NODE, TABLE_REFERENCE_RULES, TABLE_REFERENCE_RULE
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/AbstractShardingPlanningToolHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/AbstractShardingPlanningToolHandler.java
index ce2d50cd5cc..0be11eff54c 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/AbstractShardingPlanningToolHandler.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/AbstractShardingPlanningToolHandler.java
@@ -21,33 +21,13 @@ import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
import org.apache.shardingsphere.mcp.api.tool.MCPToolHandler;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
-import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingWorkflowPlanningService;
import org.apache.shardingsphere.mcp.support.protocol.response.MCPMapResponse;
import org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanPayloadBuilder;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningArguments;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRequestBinder;
-
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
abstract class AbstractShardingPlanningToolHandler implements MCPToolHandler<MCPWorkflowHandlerContext> {
- private final ShardingWorkflowPlanningService planningService;
-
- AbstractShardingPlanningToolHandler() {
- planningService = new ShardingWorkflowPlanningService();
- }
-
- AbstractShardingPlanningToolHandler(final ShardingWorkflowPlanningService planningService) {
- this.planningService = planningService;
- }
-
@Override
public Class<MCPWorkflowHandlerContext> getContextType() {
return MCPWorkflowHandlerContext.class;
@@ -55,100 +35,12 @@ abstract class AbstractShardingPlanningToolHandler implements MCPToolHandler<MCP
@Override
public MCPResponse handle(final MCPWorkflowHandlerContext workflowContext, final MCPToolCall toolCall) {
- ShardingWorkflowRequest request = WorkflowRequestBinder.bindPlanningRequest(ShardingWorkflowRequest::new, toolCall.getArguments(),
- this::bindFeatureArguments, this::applyStructuredIntentEvidence, this::applyStructuredIntentEvidence);
+ ShardingWorkflowRequest request = bindRequest(toolCall);
WorkflowContextSnapshot snapshot = plan(workflowContext, toolCall, request);
return new MCPMapResponse(WorkflowPlanPayloadBuilder.buildRuleDistSQLOnly(snapshot, snapshot.getRequest()));
}
- protected abstract WorkflowContextSnapshot plan(MCPWorkflowHandlerContext workflowContext, MCPToolCall toolCall, ShardingWorkflowRequest request);
-
- protected ShardingWorkflowPlanningService getPlanningService() {
- return planningService;
- }
+ protected abstract ShardingWorkflowRequest bindRequest(MCPToolCall toolCall);
- private void bindFeatureArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
- applyStringArgument(workflowPlanningArguments, "rule", request::setRuleName);
- applyStringArgument(workflowPlanningArguments, "data_nodes", request::setDataNodes);
- applyStringArgument(workflowPlanningArguments, "storage_units", request::setStorageUnits);
- applyStringArgument(workflowPlanningArguments, "strategy_type", request::setStrategyType);
- applyStringArgument(workflowPlanningArguments, "sharding_columns", request::setShardingColumns);
- applyStringArgument(workflowPlanningArguments, "default_strategy_type", request::setDefaultStrategyType);
- applyStringArgument(workflowPlanningArguments, "algorithm_type", request::setAlgorithmType);
- applyStringArgument(workflowPlanningArguments, "key_generate_column", request::setKeyGenerateColumn);
- applyStringArgument(workflowPlanningArguments, "key_generator", request::setKeyGeneratorName);
- applyStringArgument(workflowPlanningArguments, "key_generator_type", request::setKeyGeneratorType);
- applyStringArgument(workflowPlanningArguments, "sequence", request::setSequenceName);
- applyStringArgument(workflowPlanningArguments, "key_generate_strategy", request::setKeyGenerateStrategyName);
- applyStringArgument(workflowPlanningArguments, "component_type", request::setComponentType);
- applyStringArgument(workflowPlanningArguments, "component_name", request::setComponentName);
- applyStringArgument(workflowPlanningArguments, "allow_hint_disable", request::setAllowHintDisable);
- request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
- request.putKeyGeneratorProperties(workflowPlanningArguments.getMapArgument("key_generator_properties"));
- request.getReferenceTables().addAll(createStringList(workflowPlanningArguments.getStringArgument("reference_tables")));
- request.getAuditorNames().addAll(createStringList(workflowPlanningArguments.getStringArgument("auditors")));
- }
-
- private void applyStructuredIntentEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
- applyStringField(values, "table", request::setTable);
- applyStringField(values, "column", request::setColumn);
- applyStringField(values, "rule", request::setRuleName);
- applyStringField(values, "data_nodes", request::setDataNodes);
- applyStringField(values, "storage_units", request::setStorageUnits);
- applyStringField(values, "strategy_type", request::setStrategyType);
- applyStringField(values, "sharding_columns", request::setShardingColumns);
- applyStringField(values, "default_strategy_type", request::setDefaultStrategyType);
- applyStringField(values, "algorithm_type", request::setAlgorithmType);
- applyStringField(values, "key_generate_column", request::setKeyGenerateColumn);
- applyStringField(values, "key_generator", request::setKeyGeneratorName);
- applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
- applyStringField(values, "sequence", request::setSequenceName);
- applyStringField(values, "key_generate_strategy", request::setKeyGenerateStrategyName);
- applyStringField(values, "component_type", request::setComponentType);
- applyStringField(values, "component_name", request::setComponentName);
- applyStringField(values, "allow_hint_disable", request::setAllowHintDisable);
- applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
- applyMapField(values, "key_generator_properties", request::putKeyGeneratorProperties);
- request.getReferenceTables().addAll(createStringList(values.get("reference_tables")));
- request.getAuditorNames().addAll(createStringList(values.get("auditors")));
- }
-
- private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
- final String value = workflowPlanningArguments.getStringArgument(fieldName);
- if (!value.isEmpty()) {
- consumer.accept(value);
- }
- }
-
- private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
- Object value = values.get(fieldName);
- if (null != value) {
- consumer.accept(String.valueOf(value));
- }
- }
-
- private void applyMapField(final Map<String, Object> values, final String fieldName, final Consumer<Map<String, String>> consumer) {
- Object value = values.get(fieldName);
- if (value instanceof Map) {
- consumer.accept(createStringMap((Map<?, ?>) value));
- }
- }
-
- private Map<String, String> createStringMap(final Map<?, ?> values) {
- Map<String, String> result = new LinkedHashMap<>(values.size(), 1F);
- for (Entry<?, ?> entry : values.entrySet()) {
- result.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
- }
- return result;
- }
-
- private List<String> createStringList(final Object value) {
- if (value instanceof List) {
- return ((List<?>) value).stream().map(String::valueOf).map(String::trim).filter(each -> !each.isEmpty()).toList();
- }
- if (null == value) {
- return List.of();
- }
- return Stream.of(String.valueOf(value).split(",")).map(String::trim).filter(each -> !each.isEmpty()).toList();
- }
+ protected abstract WorkflowContextSnapshot plan(MCPWorkflowHandlerContext workflowContext, MCPToolCall toolCall, ShardingWorkflowRequest request);
}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingDefaultStrategyToolHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingDefaultStrategyToolHandler.java
index 042d8ebfd06..b7d12d1d4e7 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingDefaultStrategyToolHandler.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingDefaultStrategyToolHandler.java
@@ -19,7 +19,9 @@ package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingDefaultStrategyWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingDefaultStrategyWorkflowPlanningService;
import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingWorkflowPlanningService;
import org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -29,11 +31,16 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
*/
public final class PlanShardingDefaultStrategyToolHandler extends AbstractShardingPlanningToolHandler {
+ private final ShardingPlanningRequestBinder requestBinder = new ShardingPlanningRequestBinder();
+
+ private final ShardingDefaultStrategyWorkflowPlanningService planningService;
+
public PlanShardingDefaultStrategyToolHandler() {
+ planningService = new ShardingDefaultStrategyWorkflowPlanningService();
}
PlanShardingDefaultStrategyToolHandler(final ShardingWorkflowPlanningService planningService) {
- super(planningService);
+ this.planningService = new ShardingDefaultStrategyWorkflowPlanningService(planningService);
}
@Override
@@ -41,9 +48,14 @@ public final class PlanShardingDefaultStrategyToolHandler extends AbstractShardi
return ShardingFeatureDefinition.PLAN_DEFAULT_STRATEGY_TOOL_NAME;
}
+ @Override
+ protected ShardingWorkflowRequest bindRequest(final MCPToolCall toolCall) {
+ return requestBinder.bindDefaultStrategy(toolCall.getArguments()).toWorkflowRequest();
+ }
+
@Override
protected WorkflowContextSnapshot plan(final MCPWorkflowHandlerContext workflowContext, final MCPToolCall toolCall, final ShardingWorkflowRequest request) {
- return getPlanningService().planDefaultStrategy(
- workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(), request);
+ return planningService.plan(workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(),
+ new ShardingDefaultStrategyWorkflowRequest(request));
}
}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGenerateStrategyToolHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGenerateStrategyToolHandler.java
index a9f6617537f..533beadb19c 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGenerateStrategyToolHandler.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGenerateStrategyToolHandler.java
@@ -19,7 +19,9 @@ package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGenerateStrategyWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingKeyGenerateStrategyWorkflowPlanningService;
import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingWorkflowPlanningService;
import org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -29,11 +31,16 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
*/
public final class PlanShardingKeyGenerateStrategyToolHandler extends AbstractShardingPlanningToolHandler {
+ private final ShardingPlanningRequestBinder requestBinder = new ShardingPlanningRequestBinder();
+
+ private final ShardingKeyGenerateStrategyWorkflowPlanningService planningService;
+
public PlanShardingKeyGenerateStrategyToolHandler() {
+ planningService = new ShardingKeyGenerateStrategyWorkflowPlanningService();
}
PlanShardingKeyGenerateStrategyToolHandler(final ShardingWorkflowPlanningService planningService) {
- super(planningService);
+ this.planningService = new ShardingKeyGenerateStrategyWorkflowPlanningService(planningService);
}
@Override
@@ -41,9 +48,14 @@ public final class PlanShardingKeyGenerateStrategyToolHandler extends AbstractSh
return ShardingFeatureDefinition.PLAN_KEY_GENERATE_STRATEGY_TOOL_NAME;
}
+ @Override
+ protected ShardingWorkflowRequest bindRequest(final MCPToolCall toolCall) {
+ return requestBinder.bindKeyGenerateStrategy(toolCall.getArguments()).toWorkflowRequest();
+ }
+
@Override
protected WorkflowContextSnapshot plan(final MCPWorkflowHandlerContext workflowContext, final MCPToolCall toolCall, final ShardingWorkflowRequest request) {
- return getPlanningService().planKeyGenerateStrategy(
- workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(), request);
+ return planningService.plan(workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(),
+ new ShardingKeyGenerateStrategyWorkflowRequest(request));
}
}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGeneratorToolHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGeneratorToolHandler.java
index dd8d1db5904..cac915da6d7 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGeneratorToolHandler.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingKeyGeneratorToolHandler.java
@@ -19,7 +19,9 @@ package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGeneratorWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingKeyGeneratorWorkflowPlanningService;
import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingWorkflowPlanningService;
import org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -29,11 +31,16 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
*/
public final class PlanShardingKeyGeneratorToolHandler extends AbstractShardingPlanningToolHandler {
+ private final ShardingPlanningRequestBinder requestBinder = new ShardingPlanningRequestBinder();
+
+ private final ShardingKeyGeneratorWorkflowPlanningService planningService;
+
public PlanShardingKeyGeneratorToolHandler() {
+ planningService = new ShardingKeyGeneratorWorkflowPlanningService();
}
PlanShardingKeyGeneratorToolHandler(final ShardingWorkflowPlanningService planningService) {
- super(planningService);
+ this.planningService = new ShardingKeyGeneratorWorkflowPlanningService(planningService);
}
@Override
@@ -41,9 +48,14 @@ public final class PlanShardingKeyGeneratorToolHandler extends AbstractShardingP
return ShardingFeatureDefinition.PLAN_KEY_GENERATOR_TOOL_NAME;
}
+ @Override
+ protected ShardingWorkflowRequest bindRequest(final MCPToolCall toolCall) {
+ return requestBinder.bindKeyGenerator(toolCall.getArguments()).toWorkflowRequest();
+ }
+
@Override
protected WorkflowContextSnapshot plan(final MCPWorkflowHandlerContext workflowContext, final MCPToolCall toolCall, final ShardingWorkflowRequest request) {
- return getPlanningService().planKeyGenerator(
- workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(), request);
+ return planningService.plan(workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(),
+ new ShardingKeyGeneratorWorkflowRequest(request));
}
}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingRuleComponentCleanupToolHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingRuleComponentCleanupToolHandler.java
index b3d6988cf4f..846381f8951 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingRuleComponentCleanupToolHandler.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingRuleComponentCleanupToolHandler.java
@@ -19,7 +19,9 @@ package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingRuleComponentCleanupWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingRuleComponentCleanupWorkflowPlanningService;
import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingWorkflowPlanningService;
import org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -29,11 +31,16 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
*/
public final class PlanShardingRuleComponentCleanupToolHandler extends AbstractShardingPlanningToolHandler {
+ private final ShardingPlanningRequestBinder requestBinder = new ShardingPlanningRequestBinder();
+
+ private final ShardingRuleComponentCleanupWorkflowPlanningService planningService;
+
public PlanShardingRuleComponentCleanupToolHandler() {
+ planningService = new ShardingRuleComponentCleanupWorkflowPlanningService();
}
PlanShardingRuleComponentCleanupToolHandler(final ShardingWorkflowPlanningService planningService) {
- super(planningService);
+ this.planningService = new ShardingRuleComponentCleanupWorkflowPlanningService(planningService);
}
@Override
@@ -41,9 +48,14 @@ public final class PlanShardingRuleComponentCleanupToolHandler extends AbstractS
return ShardingFeatureDefinition.PLAN_COMPONENT_CLEANUP_TOOL_NAME;
}
+ @Override
+ protected ShardingWorkflowRequest bindRequest(final MCPToolCall toolCall) {
+ return requestBinder.bindRuleComponentCleanup(toolCall.getArguments()).toWorkflowRequest();
+ }
+
@Override
protected WorkflowContextSnapshot plan(final MCPWorkflowHandlerContext workflowContext, final MCPToolCall toolCall, final ShardingWorkflowRequest request) {
- return getPlanningService().planComponentCleanup(
- workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(), request);
+ return planningService.plan(workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(),
+ new ShardingRuleComponentCleanupWorkflowRequest(request));
}
}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableReferenceRuleToolHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableReferenceRuleToolHandler.java
index dd12acdd01a..d90f21c1206 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableReferenceRuleToolHandler.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableReferenceRuleToolHandler.java
@@ -19,7 +19,9 @@ package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableReferenceRuleWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingTableReferenceRuleWorkflowPlanningService;
import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingWorkflowPlanningService;
import org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -29,11 +31,16 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
*/
public final class PlanShardingTableReferenceRuleToolHandler extends AbstractShardingPlanningToolHandler {
+ private final ShardingPlanningRequestBinder requestBinder = new ShardingPlanningRequestBinder();
+
+ private final ShardingTableReferenceRuleWorkflowPlanningService planningService;
+
public PlanShardingTableReferenceRuleToolHandler() {
+ planningService = new ShardingTableReferenceRuleWorkflowPlanningService();
}
PlanShardingTableReferenceRuleToolHandler(final ShardingWorkflowPlanningService planningService) {
- super(planningService);
+ this.planningService = new ShardingTableReferenceRuleWorkflowPlanningService(planningService);
}
@Override
@@ -41,9 +48,14 @@ public final class PlanShardingTableReferenceRuleToolHandler extends AbstractSha
return ShardingFeatureDefinition.PLAN_TABLE_REFERENCE_TOOL_NAME;
}
+ @Override
+ protected ShardingWorkflowRequest bindRequest(final MCPToolCall toolCall) {
+ return requestBinder.bindTableReferenceRule(toolCall.getArguments()).toWorkflowRequest();
+ }
+
@Override
protected WorkflowContextSnapshot plan(final MCPWorkflowHandlerContext workflowContext, final MCPToolCall toolCall, final ShardingWorkflowRequest request) {
- return getPlanningService().planTableReferenceRule(
- workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(), request);
+ return planningService.plan(workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(),
+ new ShardingTableReferenceRuleWorkflowRequest(request));
}
}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandler.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandler.java
index 43096b0a597..489d876f610 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandler.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/PlanShardingTableRuleToolHandler.java
@@ -19,7 +19,9 @@ package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolCall;
import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableRuleWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingTableRuleWorkflowPlanningService;
import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingWorkflowPlanningService;
import org.apache.shardingsphere.mcp.support.workflow.MCPWorkflowHandlerContext;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -29,11 +31,16 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnaps
*/
public final class PlanShardingTableRuleToolHandler extends AbstractShardingPlanningToolHandler {
+ private final ShardingPlanningRequestBinder requestBinder = new ShardingPlanningRequestBinder();
+
+ private final ShardingTableRuleWorkflowPlanningService planningService;
+
public PlanShardingTableRuleToolHandler() {
+ planningService = new ShardingTableRuleWorkflowPlanningService();
}
PlanShardingTableRuleToolHandler(final ShardingWorkflowPlanningService planningService) {
- super(planningService);
+ this.planningService = new ShardingTableRuleWorkflowPlanningService(planningService);
}
@Override
@@ -41,9 +48,14 @@ public final class PlanShardingTableRuleToolHandler extends AbstractShardingPlan
return ShardingFeatureDefinition.PLAN_TABLE_RULE_TOOL_NAME;
}
+ @Override
+ protected ShardingWorkflowRequest bindRequest(final MCPToolCall toolCall) {
+ return requestBinder.bindTableRule(toolCall.getArguments()).toWorkflowRequest();
+ }
+
@Override
protected WorkflowContextSnapshot plan(final MCPWorkflowHandlerContext workflowContext, final MCPToolCall toolCall, final ShardingWorkflowRequest request) {
- return getPlanningService().planTableRule(
- workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(), request);
+ return planningService.plan(workflowContext.getWorkflowSessionContext(), workflowContext.getDatabaseContext().getQueryFacade(), toolCall.getSessionId(),
+ new ShardingTableRuleWorkflowRequest(request));
}
}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinder.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinder.java
new file mode 100644
index 00000000000..78392b9dcd4
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinder.java
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingDefaultStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGenerateStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGeneratorWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingRuleComponentCleanupWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableReferenceRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningArguments;
+import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRequestBinder;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+/**
+ * Sharding planning request binder.
+ */
+public final class ShardingPlanningRequestBinder {
+
+ /**
+ * Bind sharding table rule workflow request.
+ *
+ * @param arguments raw MCP arguments
+ * @return sharding table rule workflow request
+ */
+ public ShardingTableRuleWorkflowRequest bindTableRule(final Map<String, Object> arguments) {
+ return new ShardingTableRuleWorkflowRequest(bind(arguments, this::bindTableRuleArguments, this::applyTableRuleEvidence));
+ }
+
+ /**
+ * Bind sharding table reference rule workflow request.
+ *
+ * @param arguments raw MCP arguments
+ * @return sharding table reference rule workflow request
+ */
+ public ShardingTableReferenceRuleWorkflowRequest bindTableReferenceRule(final Map<String, Object> arguments) {
+ return new ShardingTableReferenceRuleWorkflowRequest(bind(arguments, this::bindTableReferenceRuleArguments, this::applyTableReferenceRuleEvidence));
+ }
+
+ /**
+ * Bind sharding default strategy workflow request.
+ *
+ * @param arguments raw MCP arguments
+ * @return sharding default strategy workflow request
+ */
+ public ShardingDefaultStrategyWorkflowRequest bindDefaultStrategy(final Map<String, Object> arguments) {
+ return new ShardingDefaultStrategyWorkflowRequest(bind(arguments, this::bindDefaultStrategyArguments, this::applyDefaultStrategyEvidence));
+ }
+
+ /**
+ * Bind sharding key generator workflow request.
+ *
+ * @param arguments raw MCP arguments
+ * @return sharding key generator workflow request
+ */
+ public ShardingKeyGeneratorWorkflowRequest bindKeyGenerator(final Map<String, Object> arguments) {
+ return new ShardingKeyGeneratorWorkflowRequest(bind(arguments, this::bindKeyGeneratorArguments, this::applyKeyGeneratorEvidence));
+ }
+
+ /**
+ * Bind sharding key generate strategy workflow request.
+ *
+ * @param arguments raw MCP arguments
+ * @return sharding key generate strategy workflow request
+ */
+ public ShardingKeyGenerateStrategyWorkflowRequest bindKeyGenerateStrategy(final Map<String, Object> arguments) {
+ return new ShardingKeyGenerateStrategyWorkflowRequest(bind(arguments, this::bindKeyGenerateStrategyArguments, this::applyKeyGenerateStrategyEvidence));
+ }
+
+ /**
+ * Bind sharding rule component cleanup workflow request.
+ *
+ * @param arguments raw MCP arguments
+ * @return sharding rule component cleanup workflow request
+ */
+ public ShardingRuleComponentCleanupWorkflowRequest bindRuleComponentCleanup(final Map<String, Object> arguments) {
+ return new ShardingRuleComponentCleanupWorkflowRequest(bind(arguments, this::bindRuleComponentCleanupArguments, this::applyRuleComponentCleanupEvidence));
+ }
+
+ private ShardingWorkflowRequest bind(final Map<String, Object> arguments, final FeatureArgumentBinder argumentBinder, final EvidenceBinder evidenceBinder) {
+ return WorkflowRequestBinder.bindPlanningRequest(ShardingWorkflowRequest::new, arguments, argumentBinder::bind, evidenceBinder::bind, evidenceBinder::bind);
+ }
+
+ private void bindTableRuleArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
+ applyStringArgument(workflowPlanningArguments, "rule", request::setRuleName);
+ applyStringArgument(workflowPlanningArguments, "data_nodes", request::setDataNodes);
+ applyStringArgument(workflowPlanningArguments, "storage_units", request::setStorageUnits);
+ applyStringArgument(workflowPlanningArguments, "strategy_type", request::setStrategyType);
+ applyStringArgument(workflowPlanningArguments, "sharding_columns", request::setShardingColumns);
+ applyStringArgument(workflowPlanningArguments, "algorithm_type", request::setAlgorithmType);
+ applyStringArgument(workflowPlanningArguments, "key_generate_column", request::setKeyGenerateColumn);
+ applyStringArgument(workflowPlanningArguments, "key_generator", request::setKeyGeneratorName);
+ applyStringArgument(workflowPlanningArguments, "key_generator_type", request::setKeyGeneratorType);
+ applyStringArgument(workflowPlanningArguments, "allow_hint_disable", request::setAllowHintDisable);
+ request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
+ request.putKeyGeneratorProperties(workflowPlanningArguments.getMapArgument("key_generator_properties"));
+ request.getAuditorNames().addAll(createStringList(workflowPlanningArguments.getStringArgument("auditors")));
+ }
+
+ private void applyTableRuleEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
+ applyStringField(values, "table", request::setTable);
+ applyStringField(values, "column", request::setColumn);
+ applyStringField(values, "rule", request::setRuleName);
+ applyStringField(values, "data_nodes", request::setDataNodes);
+ applyStringField(values, "storage_units", request::setStorageUnits);
+ applyStringField(values, "strategy_type", request::setStrategyType);
+ applyStringField(values, "sharding_columns", request::setShardingColumns);
+ applyStringField(values, "algorithm_type", request::setAlgorithmType);
+ applyStringField(values, "key_generate_column", request::setKeyGenerateColumn);
+ applyStringField(values, "key_generator", request::setKeyGeneratorName);
+ applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
+ applyStringField(values, "allow_hint_disable", request::setAllowHintDisable);
+ applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
+ applyMapField(values, "key_generator_properties", request::putKeyGeneratorProperties);
+ request.getAuditorNames().addAll(createStringList(values.get("auditors")));
+ }
+
+ private void bindTableReferenceRuleArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
+ applyStringArgument(workflowPlanningArguments, "rule", request::setRuleName);
+ request.getReferenceTables().addAll(createStringList(workflowPlanningArguments.getStringArgument("reference_tables")));
+ }
+
+ private void applyTableReferenceRuleEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
+ applyStringField(values, "rule", request::setRuleName);
+ request.getReferenceTables().addAll(createStringList(values.get("reference_tables")));
+ }
+
+ private void bindDefaultStrategyArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
+ applyStringArgument(workflowPlanningArguments, "default_strategy_type", request::setDefaultStrategyType);
+ applyStringArgument(workflowPlanningArguments, "strategy_type", request::setStrategyType);
+ applyStringArgument(workflowPlanningArguments, "sharding_columns", request::setShardingColumns);
+ applyStringArgument(workflowPlanningArguments, "algorithm_type", request::setAlgorithmType);
+ request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
+ }
+
+ private void applyDefaultStrategyEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
+ applyStringField(values, "column", request::setColumn);
+ applyStringField(values, "default_strategy_type", request::setDefaultStrategyType);
+ applyStringField(values, "strategy_type", request::setStrategyType);
+ applyStringField(values, "sharding_columns", request::setShardingColumns);
+ applyStringField(values, "algorithm_type", request::setAlgorithmType);
+ applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
+ }
+
+ private void bindKeyGeneratorArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
+ applyStringArgument(workflowPlanningArguments, "key_generator", request::setKeyGeneratorName);
+ applyStringArgument(workflowPlanningArguments, "key_generator_type", request::setKeyGeneratorType);
+ applyStringArgument(workflowPlanningArguments, "algorithm_type", request::setAlgorithmType);
+ request.putKeyGeneratorProperties(workflowPlanningArguments.getMapArgument("key_generator_properties"));
+ request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
+ }
+
+ private void applyKeyGeneratorEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
+ applyStringField(values, "key_generator", request::setKeyGeneratorName);
+ applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
+ applyStringField(values, "algorithm_type", request::setAlgorithmType);
+ applyMapField(values, "key_generator_properties", request::putKeyGeneratorProperties);
+ applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
+ }
+
+ private void bindKeyGenerateStrategyArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
+ applyStringArgument(workflowPlanningArguments, "key_generate_strategy", request::setKeyGenerateStrategyName);
+ applyStringArgument(workflowPlanningArguments, "sequence", request::setSequenceName);
+ applyStringArgument(workflowPlanningArguments, "key_generator", request::setKeyGeneratorName);
+ applyStringArgument(workflowPlanningArguments, "key_generator_type", request::setKeyGeneratorType);
+ applyStringArgument(workflowPlanningArguments, "algorithm_type", request::setAlgorithmType);
+ request.putKeyGeneratorProperties(workflowPlanningArguments.getMapArgument("key_generator_properties"));
+ request.putAlgorithmProperties(workflowPlanningArguments.getMapArgument("algorithm_properties"));
+ }
+
+ private void applyKeyGenerateStrategyEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
+ applyStringField(values, "table", request::setTable);
+ applyStringField(values, "column", request::setColumn);
+ applyStringField(values, "key_generate_strategy", request::setKeyGenerateStrategyName);
+ applyStringField(values, "sequence", request::setSequenceName);
+ applyStringField(values, "key_generator", request::setKeyGeneratorName);
+ applyStringField(values, "key_generator_type", request::setKeyGeneratorType);
+ applyStringField(values, "algorithm_type", request::setAlgorithmType);
+ applyMapField(values, "key_generator_properties", request::putKeyGeneratorProperties);
+ applyMapField(values, "algorithm_properties", request::putAlgorithmProperties);
+ }
+
+ private void bindRuleComponentCleanupArguments(final ShardingWorkflowRequest request, final WorkflowPlanningArguments workflowPlanningArguments) {
+ applyStringArgument(workflowPlanningArguments, "component_type", request::setComponentType);
+ applyStringArgument(workflowPlanningArguments, "component_name", request::setComponentName);
+ }
+
+ private void applyRuleComponentCleanupEvidence(final ShardingWorkflowRequest request, final Map<String, Object> values) {
+ applyStringField(values, "component_type", request::setComponentType);
+ applyStringField(values, "component_name", request::setComponentName);
+ }
+
+ private void applyStringArgument(final WorkflowPlanningArguments workflowPlanningArguments, final String fieldName, final Consumer<String> consumer) {
+ String value = workflowPlanningArguments.getStringArgument(fieldName);
+ if (!value.isEmpty()) {
+ consumer.accept(value);
+ }
+ }
+
+ private void applyStringField(final Map<String, Object> values, final String fieldName, final Consumer<String> consumer) {
+ Object value = values.get(fieldName);
+ if (null != value) {
+ consumer.accept(String.valueOf(value));
+ }
+ }
+
+ private void applyMapField(final Map<String, Object> values, final String fieldName, final Consumer<Map<String, String>> consumer) {
+ Object value = values.get(fieldName);
+ if (value instanceof Map) {
+ consumer.accept(createStringMap((Map<?, ?>) value));
+ }
+ }
+
+ private Map<String, String> createStringMap(final Map<?, ?> values) {
+ Map<String, String> result = new LinkedHashMap<>(values.size(), 1F);
+ for (Entry<?, ?> entry : values.entrySet()) {
+ result.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
+ }
+ return result;
+ }
+
+ private List<String> createStringList(final Object value) {
+ if (value instanceof List) {
+ return ((List<?>) value).stream().map(String::valueOf).map(String::trim).filter(each -> !each.isEmpty()).toList();
+ }
+ if (null == value) {
+ return List.of();
+ }
+ return Stream.of(String.valueOf(value).split(",")).map(String::trim).filter(each -> !each.isEmpty()).toList();
+ }
+
+ @FunctionalInterface
+ private interface FeatureArgumentBinder {
+
+ void bind(ShardingWorkflowRequest request, WorkflowPlanningArguments workflowPlanningArguments);
+ }
+
+ @FunctionalInterface
+ private interface EvidenceBinder {
+
+ void bind(ShardingWorkflowRequest request, Map<String, Object> values);
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/AbstractShardingSpecificWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/AbstractShardingSpecificWorkflowRequest.java
new file mode 100644
index 00000000000..2abbd792232
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/AbstractShardingSpecificWorkflowRequest.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+abstract class AbstractShardingSpecificWorkflowRequest {
+
+ private final ShardingWorkflowRequest workflowRequest;
+
+ AbstractShardingSpecificWorkflowRequest(final ShardingWorkflowRequest workflowRequest) {
+ this.workflowRequest = workflowRequest.copy();
+ }
+
+ ShardingWorkflowRequest copyWorkflowRequest() {
+ return workflowRequest.copy();
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingDefaultStrategyWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingDefaultStrategyWorkflowRequest.java
new file mode 100644
index 00000000000..db1ee5620f8
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingDefaultStrategyWorkflowRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+/**
+ * Sharding default strategy workflow request.
+ */
+public final class ShardingDefaultStrategyWorkflowRequest extends AbstractShardingSpecificWorkflowRequest {
+
+ /**
+ * Create sharding default strategy workflow request.
+ *
+ * @param workflowRequest generic sharding workflow request
+ */
+ public ShardingDefaultStrategyWorkflowRequest(final ShardingWorkflowRequest workflowRequest) {
+ super(workflowRequest);
+ }
+
+ /**
+ * Convert to generic sharding workflow request.
+ *
+ * @return generic sharding workflow request
+ */
+ public ShardingWorkflowRequest toWorkflowRequest() {
+ return copyWorkflowRequest();
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGenerateStrategyWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGenerateStrategyWorkflowRequest.java
new file mode 100644
index 00000000000..877febc71a0
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGenerateStrategyWorkflowRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+/**
+ * Sharding key generate strategy workflow request.
+ */
+public final class ShardingKeyGenerateStrategyWorkflowRequest extends AbstractShardingSpecificWorkflowRequest {
+
+ /**
+ * Create sharding key generate strategy workflow request.
+ *
+ * @param workflowRequest generic sharding workflow request
+ */
+ public ShardingKeyGenerateStrategyWorkflowRequest(final ShardingWorkflowRequest workflowRequest) {
+ super(workflowRequest);
+ }
+
+ /**
+ * Convert to generic sharding workflow request.
+ *
+ * @return generic sharding workflow request
+ */
+ public ShardingWorkflowRequest toWorkflowRequest() {
+ return copyWorkflowRequest();
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGeneratorWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGeneratorWorkflowRequest.java
new file mode 100644
index 00000000000..5e19fc15b58
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGeneratorWorkflowRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+/**
+ * Sharding key generator workflow request.
+ */
+public final class ShardingKeyGeneratorWorkflowRequest extends AbstractShardingSpecificWorkflowRequest {
+
+ /**
+ * Create sharding key generator workflow request.
+ *
+ * @param workflowRequest generic sharding workflow request
+ */
+ public ShardingKeyGeneratorWorkflowRequest(final ShardingWorkflowRequest workflowRequest) {
+ super(workflowRequest);
+ }
+
+ /**
+ * Convert to generic sharding workflow request.
+ *
+ * @return generic sharding workflow request
+ */
+ public ShardingWorkflowRequest toWorkflowRequest() {
+ return copyWorkflowRequest();
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingRuleComponentCleanupWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingRuleComponentCleanupWorkflowRequest.java
new file mode 100644
index 00000000000..ea2192084f2
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingRuleComponentCleanupWorkflowRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+/**
+ * Sharding rule component cleanup workflow request.
+ */
+public final class ShardingRuleComponentCleanupWorkflowRequest extends AbstractShardingSpecificWorkflowRequest {
+
+ /**
+ * Create sharding rule component cleanup workflow request.
+ *
+ * @param workflowRequest generic sharding workflow request
+ */
+ public ShardingRuleComponentCleanupWorkflowRequest(final ShardingWorkflowRequest workflowRequest) {
+ super(workflowRequest);
+ }
+
+ /**
+ * Convert to generic sharding workflow request.
+ *
+ * @return generic sharding workflow request
+ */
+ public ShardingWorkflowRequest toWorkflowRequest() {
+ return copyWorkflowRequest();
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableReferenceRuleWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableReferenceRuleWorkflowRequest.java
new file mode 100644
index 00000000000..5ffec1c6e7c
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableReferenceRuleWorkflowRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+/**
+ * Sharding table reference rule workflow request.
+ */
+public final class ShardingTableReferenceRuleWorkflowRequest extends AbstractShardingSpecificWorkflowRequest {
+
+ /**
+ * Create sharding table reference rule workflow request.
+ *
+ * @param workflowRequest generic sharding workflow request
+ */
+ public ShardingTableReferenceRuleWorkflowRequest(final ShardingWorkflowRequest workflowRequest) {
+ super(workflowRequest);
+ }
+
+ /**
+ * Convert to generic sharding workflow request.
+ *
+ * @return generic sharding workflow request
+ */
+ public ShardingWorkflowRequest toWorkflowRequest() {
+ return copyWorkflowRequest();
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableRuleWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableRuleWorkflowRequest.java
new file mode 100644
index 00000000000..7f499eba4da
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableRuleWorkflowRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+/**
+ * Sharding table rule workflow request.
+ */
+public final class ShardingTableRuleWorkflowRequest extends AbstractShardingSpecificWorkflowRequest {
+
+ /**
+ * Create sharding table rule workflow request.
+ *
+ * @param workflowRequest generic sharding workflow request
+ */
+ public ShardingTableRuleWorkflowRequest(final ShardingWorkflowRequest workflowRequest) {
+ super(workflowRequest);
+ }
+
+ /**
+ * Convert to generic sharding workflow request.
+ *
+ * @return generic sharding workflow request
+ */
+ public ShardingWorkflowRequest toWorkflowRequest() {
+ return copyWorkflowRequest();
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingWorkflowRequest.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingWorkflowRequest.java
index 59fa0996d07..0855bd2b93f 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingWorkflowRequest.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingWorkflowRequest.java
@@ -178,6 +178,11 @@ public final class ShardingWorkflowRequest extends WorkflowRequest {
keyGeneratorProperties.putAll(props);
}
+ @Override
+ public Map<String, String> getAlgorithmProperties(final String algorithmRole) {
+ return "key_generator".equals(algorithmRole) ? keyGeneratorProperties : super.getAlgorithmProperties(algorithmRole);
+ }
+
private void overlayTo(final ShardingWorkflowRequest target) {
super.overlayTo(target);
target.setRuleName(resolveValue(target.getRuleName(), ruleName));
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmPropertyTemplateService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmPropertyTemplateService.java
new file mode 100644
index 00000000000..a6e14493956
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmPropertyTemplateService.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Sharding algorithm and key-generator property template service.
+ */
+public final class ShardingAlgorithmPropertyTemplateService {
+
+ /**
+ * Find sharding algorithm requirements.
+ *
+ * @param algorithmType sharding algorithm type
+ * @return property requirements
+ */
+ public List<AlgorithmPropertyRequirement> findAlgorithmRequirements(final String algorithmType) {
+ String actualAlgorithmType = Objects.toString(algorithmType, "").trim().toUpperCase(Locale.ENGLISH);
+ if ("MOD".equals(actualAlgorithmType) || "HASH_MOD".equals(actualAlgorithmType)) {
+ return List.of(new AlgorithmPropertyRequirement("primary", "sharding-count", true, false, "Number of shards.", ""));
+ }
+ if ("INLINE".equals(actualAlgorithmType) || "COMPLEX_INLINE".equals(actualAlgorithmType) || "HINT_INLINE".equals(actualAlgorithmType)) {
+ return List.of(new AlgorithmPropertyRequirement("primary", "algorithm-expression", true, false, "Inline sharding expression.", ""));
+ }
+ return List.of();
+ }
+
+ /**
+ * Find key-generator requirements.
+ *
+ * @param keyGeneratorType key-generator type
+ * @return property requirements
+ */
+ public List<AlgorithmPropertyRequirement> findKeyGeneratorRequirements(final String keyGeneratorType) {
+ String actualKeyGeneratorType = Objects.toString(keyGeneratorType, "").trim().toUpperCase(Locale.ENGLISH);
+ if (!"SNOWFLAKE".equals(actualKeyGeneratorType)) {
+ return List.of();
+ }
+ return List.of(
+ new AlgorithmPropertyRequirement("key_generator", "worker-id", false, false, "Worker ID for Snowflake when deployment requires a fixed worker.", ""),
+ new AlgorithmPropertyRequirement("key_generator", "max-vibration-offset", false, false, "Maximum vibration offset for Snowflake.", ""));
+ }
+
+ /**
+ * Find combined requirements.
+ *
+ * @param algorithmType sharding algorithm type
+ * @param keyGeneratorType key-generator type
+ * @return property requirements
+ */
+ public List<AlgorithmPropertyRequirement> findRequirements(final String algorithmType, final String keyGeneratorType) {
+ List<AlgorithmPropertyRequirement> result = new LinkedList<>();
+ result.addAll(findAlgorithmRequirements(algorithmType));
+ result.addAll(findKeyGeneratorRequirements(keyGeneratorType));
+ return result;
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationService.java
new file mode 100644
index 00000000000..8f8899e3b1a
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationService.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Sharding algorithm and key-generator recommendation service.
+ */
+public final class ShardingAlgorithmRecommendationService {
+
+ /**
+ * Recommend sharding algorithm and key generator.
+ *
+ * @param request workflow request
+ * @param algorithmRows algorithm rows
+ * @param keyGeneratorRows key-generator rows
+ * @param includeShardingAlgorithm whether to include sharding algorithm recommendation
+ * @param includeKeyGenerator whether to include key-generator recommendation
+ * @param issues workflow issues
+ * @return selected candidates
+ */
+ public List<AlgorithmCandidate> recommend(final ShardingWorkflowRequest request, final List<Map<String, Object>> algorithmRows,
+ final List<Map<String, Object>> keyGeneratorRows, final boolean includeShardingAlgorithm,
+ final boolean includeKeyGenerator, final List<WorkflowIssue> issues) {
+ List<Map<String, Object>> actualAlgorithmRows = null == algorithmRows ? List.of() : algorithmRows;
+ List<Map<String, Object>> actualKeyGeneratorRows = null == keyGeneratorRows ? List.of() : keyGeneratorRows;
+ List<AlgorithmCandidate> result = new LinkedList<>();
+ if (includeShardingAlgorithm) {
+ recommendAlgorithm(request.getAlgorithmType(), actualAlgorithmRows, issues).forEach(result::add);
+ }
+ if (includeKeyGenerator) {
+ recommendKeyGenerator(resolveKeyGeneratorType(request), actualKeyGeneratorRows, issues).forEach(result::add);
+ }
+ return result;
+ }
+
+ private List<AlgorithmCandidate> recommendAlgorithm(final String algorithmType, final List<Map<String, Object>> algorithmRows, final List<WorkflowIssue> issues) {
+ String actualAlgorithmType = Objects.toString(algorithmType, "").trim().toUpperCase(Locale.ENGLISH);
+ if (!actualAlgorithmType.isEmpty()) {
+ if (algorithmRows.isEmpty() || containsAlgorithm(algorithmRows, actualAlgorithmType)) {
+ return List.of(createCandidate("primary", actualAlgorithmType, 100, "User specified sharding algorithm."));
+ }
+ addAlgorithmNotFoundIssue(issues, "Sharding algorithm", actualAlgorithmType);
+ return List.of();
+ }
+ String recommendedType = resolveRecommendedAlgorithm(algorithmRows, List.of("INLINE", "MOD", "HASH_MOD"));
+ if (recommendedType.isEmpty()) {
+ addNoAlgorithmIssue(issues, "sharding algorithm");
+ return List.of();
+ }
+ return List.of(createCandidate("primary", recommendedType, 90, "Recommended from current sharding algorithm availability."));
+ }
+
+ private List<AlgorithmCandidate> recommendKeyGenerator(final String keyGeneratorType, final List<Map<String, Object>> keyGeneratorRows, final List<WorkflowIssue> issues) {
+ String actualKeyGeneratorType = Objects.toString(keyGeneratorType, "").trim().toUpperCase(Locale.ENGLISH);
+ if (!actualKeyGeneratorType.isEmpty()) {
+ if (keyGeneratorRows.isEmpty() || containsAlgorithm(keyGeneratorRows, actualKeyGeneratorType)) {
+ return List.of(createCandidate("key_generator", actualKeyGeneratorType, 100, "User specified key-generator algorithm."));
+ }
+ addAlgorithmNotFoundIssue(issues, "Key-generator algorithm", actualKeyGeneratorType);
+ return List.of();
+ }
+ String recommendedType = resolveRecommendedAlgorithm(keyGeneratorRows, List.of("SNOWFLAKE", "UUID"));
+ if (recommendedType.isEmpty()) {
+ addNoAlgorithmIssue(issues, "key-generator algorithm");
+ return List.of();
+ }
+ return List.of(createCandidate("key_generator", recommendedType, 90, "Recommended from current key-generator availability."));
+ }
+
+ private String resolveKeyGeneratorType(final ShardingWorkflowRequest request) {
+ return request.getKeyGeneratorType().isEmpty() ? request.getAlgorithmType() : request.getKeyGeneratorType();
+ }
+
+ private AlgorithmCandidate createCandidate(final String role, final String algorithmType, final int score, final String reason) {
+ return new AlgorithmCandidate(role, algorithmType, null, null, null, score, reason, "");
+ }
+
+ private String resolveRecommendedAlgorithm(final List<Map<String, Object>> algorithmRows, final List<String> preferredTypes) {
+ for (String each : preferredTypes) {
+ if (containsAlgorithm(algorithmRows, each)) {
+ return each;
+ }
+ }
+ return algorithmRows.isEmpty() ? "" : getAlgorithmType(algorithmRows.getFirst());
+ }
+
+ private boolean containsAlgorithm(final List<Map<String, Object>> algorithmRows, final String algorithmType) {
+ return algorithmRows.stream().map(this::getAlgorithmType).anyMatch(algorithmType::equals);
+ }
+
+ private String getAlgorithmType(final Map<String, Object> algorithmRow) {
+ return Objects.toString(algorithmRow.getOrDefault("type", algorithmRow.getOrDefault("name", "")), "").trim().toUpperCase(Locale.ENGLISH);
+ }
+
+ private void addAlgorithmNotFoundIssue(final List<WorkflowIssue> issues, final String label, final String algorithmType) {
+ issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+ String.format("%s `%s` is not visible from the current Proxy.", label, algorithmType),
+ "Choose an available algorithm.", false, Map.of()));
+ }
+
+ private void addNoAlgorithmIssue(final List<WorkflowIssue> issues, final String label) {
+ issues.add(new WorkflowIssue(WorkflowIssueCode.ALGORITHM_NOT_FOUND, "error", "selecting-algorithm",
+ String.format("No %s is available from the current Proxy.", label), "Install or expose an available algorithm.", false, Map.of()));
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDefaultStrategyWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDefaultStrategyWorkflowPlanningService.java
new file mode 100644
index 00000000000..116f172ecab
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDefaultStrategyWorkflowPlanningService.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingDefaultStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+
+/**
+ * Sharding default strategy workflow planning service.
+ */
+public final class ShardingDefaultStrategyWorkflowPlanningService {
+
+ private final Planner planner;
+
+ /**
+ * Create sharding default strategy workflow planning service.
+ */
+ public ShardingDefaultStrategyWorkflowPlanningService() {
+ this(new ShardingWorkflowPlanningKernel());
+ }
+
+ /**
+ * Create sharding default strategy workflow planning service with delegate.
+ *
+ * @param delegate sharding workflow planning service
+ */
+ public ShardingDefaultStrategyWorkflowPlanningService(final ShardingWorkflowPlanningService delegate) {
+ this(delegate::planDefaultStrategy);
+ }
+
+ ShardingDefaultStrategyWorkflowPlanningService(final ShardingWorkflowPlanningKernel kernel) {
+ this(kernel::planDefaultStrategy);
+ }
+
+ private ShardingDefaultStrategyWorkflowPlanningService(final Planner planner) {
+ this.planner = planner;
+ }
+
+ /**
+ * Plan default sharding strategy workflow.
+ *
+ * @param workflowSessionContext workflow session context
+ * @param queryFacade feature query facade
+ * @param sessionId MCP session ID
+ * @param request sharding default strategy workflow request
+ * @return workflow context snapshot
+ */
+ public WorkflowContextSnapshot plan(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
+ final String sessionId, final ShardingDefaultStrategyWorkflowRequest request) {
+ return planner.plan(workflowSessionContext, queryFacade, sessionId, request.toWorkflowRequest());
+ }
+
+ @FunctionalInterface
+ private interface Planner {
+
+ WorkflowContextSnapshot plan(WorkflowSessionContext workflowSessionContext, MCPFeatureQueryFacade queryFacade, String sessionId, ShardingWorkflowRequest request);
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingInspectionService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingInspectionService.java
index 6b2c6962340..7205aeba7e3 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingInspectionService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingInspectionService.java
@@ -54,7 +54,8 @@ public final class ShardingInspectionService {
private List<Map<String, Object>> queryPluginRows(final MCPFeatureQueryFacade queryFacade, final String sql, final List<Map<String, Object>> fallbackRows) {
try {
- return queryFacade.queryWithAnyDatabase(sql);
+ List<Map<String, Object>> result = queryFacade.queryWithAnyDatabase(sql);
+ return null == result ? fallbackRows : result;
} catch (final MCPQueryFailedException ex) {
if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
return fallbackRows;
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGenerateStrategyWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGenerateStrategyWorkflowPlanningService.java
new file mode 100644
index 00000000000..ed07326c047
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGenerateStrategyWorkflowPlanningService.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGenerateStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+
+/**
+ * Sharding key generate strategy workflow planning service.
+ */
+public final class ShardingKeyGenerateStrategyWorkflowPlanningService {
+
+ private final Planner planner;
+
+ /**
+ * Create sharding key generate strategy workflow planning service.
+ */
+ public ShardingKeyGenerateStrategyWorkflowPlanningService() {
+ this(new ShardingWorkflowPlanningKernel());
+ }
+
+ /**
+ * Create sharding key generate strategy workflow planning service with delegate.
+ *
+ * @param delegate sharding workflow planning service
+ */
+ public ShardingKeyGenerateStrategyWorkflowPlanningService(final ShardingWorkflowPlanningService delegate) {
+ this(delegate::planKeyGenerateStrategy);
+ }
+
+ ShardingKeyGenerateStrategyWorkflowPlanningService(final ShardingWorkflowPlanningKernel kernel) {
+ this(kernel::planKeyGenerateStrategy);
+ }
+
+ private ShardingKeyGenerateStrategyWorkflowPlanningService(final Planner planner) {
+ this.planner = planner;
+ }
+
+ /**
+ * Plan sharding key generate strategy workflow.
+ *
+ * @param workflowSessionContext workflow session context
+ * @param queryFacade feature query facade
+ * @param sessionId MCP session ID
+ * @param request sharding key generate strategy workflow request
+ * @return workflow context snapshot
+ */
+ public WorkflowContextSnapshot plan(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
+ final String sessionId, final ShardingKeyGenerateStrategyWorkflowRequest request) {
+ return planner.plan(workflowSessionContext, queryFacade, sessionId, request.toWorkflowRequest());
+ }
+
+ @FunctionalInterface
+ private interface Planner {
+
+ WorkflowContextSnapshot plan(WorkflowSessionContext workflowSessionContext, MCPFeatureQueryFacade queryFacade, String sessionId, ShardingWorkflowRequest request);
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGeneratorWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGeneratorWorkflowPlanningService.java
new file mode 100644
index 00000000000..1424342b99d
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGeneratorWorkflowPlanningService.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGeneratorWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+
+/**
+ * Sharding key generator workflow planning service.
+ */
+public final class ShardingKeyGeneratorWorkflowPlanningService {
+
+ private final Planner planner;
+
+ /**
+ * Create sharding key generator workflow planning service.
+ */
+ public ShardingKeyGeneratorWorkflowPlanningService() {
+ this(new ShardingWorkflowPlanningKernel());
+ }
+
+ /**
+ * Create sharding key generator workflow planning service with delegate.
+ *
+ * @param delegate sharding workflow planning service
+ */
+ public ShardingKeyGeneratorWorkflowPlanningService(final ShardingWorkflowPlanningService delegate) {
+ this(delegate::planKeyGenerator);
+ }
+
+ ShardingKeyGeneratorWorkflowPlanningService(final ShardingWorkflowPlanningKernel kernel) {
+ this(kernel::planKeyGenerator);
+ }
+
+ private ShardingKeyGeneratorWorkflowPlanningService(final Planner planner) {
+ this.planner = planner;
+ }
+
+ /**
+ * Plan sharding key generator workflow.
+ *
+ * @param workflowSessionContext workflow session context
+ * @param queryFacade feature query facade
+ * @param sessionId MCP session ID
+ * @param request sharding key generator workflow request
+ * @return workflow context snapshot
+ */
+ public WorkflowContextSnapshot plan(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
+ final String sessionId, final ShardingKeyGeneratorWorkflowRequest request) {
+ return planner.plan(workflowSessionContext, queryFacade, sessionId, request.toWorkflowRequest());
+ }
+
+ @FunctionalInterface
+ private interface Planner {
+
+ WorkflowContextSnapshot plan(WorkflowSessionContext workflowSessionContext, MCPFeatureQueryFacade queryFacade, String sessionId, ShardingWorkflowRequest request);
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingRuleComponentCleanupWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingRuleComponentCleanupWorkflowPlanningService.java
new file mode 100644
index 00000000000..47f52d6e0c8
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingRuleComponentCleanupWorkflowPlanningService.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingRuleComponentCleanupWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+
+/**
+ * Sharding rule component cleanup workflow planning service.
+ */
+public final class ShardingRuleComponentCleanupWorkflowPlanningService {
+
+ private final Planner planner;
+
+ /**
+ * Create sharding rule component cleanup workflow planning service.
+ */
+ public ShardingRuleComponentCleanupWorkflowPlanningService() {
+ this(new ShardingWorkflowPlanningKernel());
+ }
+
+ /**
+ * Create sharding rule component cleanup workflow planning service with delegate.
+ *
+ * @param delegate sharding workflow planning service
+ */
+ public ShardingRuleComponentCleanupWorkflowPlanningService(final ShardingWorkflowPlanningService delegate) {
+ this(delegate::planComponentCleanup);
+ }
+
+ ShardingRuleComponentCleanupWorkflowPlanningService(final ShardingWorkflowPlanningKernel kernel) {
+ this(kernel::planComponentCleanup);
+ }
+
+ private ShardingRuleComponentCleanupWorkflowPlanningService(final Planner planner) {
+ this.planner = planner;
+ }
+
+ /**
+ * Plan sharding rule component cleanup workflow.
+ *
+ * @param workflowSessionContext workflow session context
+ * @param queryFacade feature query facade
+ * @param sessionId MCP session ID
+ * @param request sharding rule component cleanup workflow request
+ * @return workflow context snapshot
+ */
+ public WorkflowContextSnapshot plan(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
+ final String sessionId, final ShardingRuleComponentCleanupWorkflowRequest request) {
+ return planner.plan(workflowSessionContext, queryFacade, sessionId, request.toWorkflowRequest());
+ }
+
+ @FunctionalInterface
+ private interface Planner {
+
+ WorkflowContextSnapshot plan(WorkflowSessionContext workflowSessionContext, MCPFeatureQueryFacade queryFacade, String sessionId, ShardingWorkflowRequest request);
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableReferenceRuleWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableReferenceRuleWorkflowPlanningService.java
new file mode 100644
index 00000000000..46db1766eac
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableReferenceRuleWorkflowPlanningService.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableReferenceRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+
+/**
+ * Sharding table reference rule workflow planning service.
+ */
+public final class ShardingTableReferenceRuleWorkflowPlanningService {
+
+ private final Planner planner;
+
+ /**
+ * Create sharding table reference rule workflow planning service.
+ */
+ public ShardingTableReferenceRuleWorkflowPlanningService() {
+ this(new ShardingWorkflowPlanningKernel());
+ }
+
+ /**
+ * Create sharding table reference rule workflow planning service with delegate.
+ *
+ * @param delegate sharding workflow planning service
+ */
+ public ShardingTableReferenceRuleWorkflowPlanningService(final ShardingWorkflowPlanningService delegate) {
+ this(delegate::planTableReferenceRule);
+ }
+
+ ShardingTableReferenceRuleWorkflowPlanningService(final ShardingWorkflowPlanningKernel kernel) {
+ this(kernel::planTableReferenceRule);
+ }
+
+ private ShardingTableReferenceRuleWorkflowPlanningService(final Planner planner) {
+ this.planner = planner;
+ }
+
+ /**
+ * Plan sharding table reference rule workflow.
+ *
+ * @param workflowSessionContext workflow session context
+ * @param queryFacade feature query facade
+ * @param sessionId MCP session ID
+ * @param request sharding table reference rule workflow request
+ * @return workflow context snapshot
+ */
+ public WorkflowContextSnapshot plan(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
+ final String sessionId, final ShardingTableReferenceRuleWorkflowRequest request) {
+ return planner.plan(workflowSessionContext, queryFacade, sessionId, request.toWorkflowRequest());
+ }
+
+ @FunctionalInterface
+ private interface Planner {
+
+ WorkflowContextSnapshot plan(WorkflowSessionContext workflowSessionContext, MCPFeatureQueryFacade queryFacade, String sessionId, ShardingWorkflowRequest request);
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableRuleWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableRuleWorkflowPlanningService.java
new file mode 100644
index 00000000000..473d7afe5ea
--- /dev/null
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableRuleWorkflowPlanningService.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+
+/**
+ * Sharding table rule workflow planning service.
+ */
+public final class ShardingTableRuleWorkflowPlanningService {
+
+ private final Planner planner;
+
+ /**
+ * Create sharding table rule workflow planning service.
+ */
+ public ShardingTableRuleWorkflowPlanningService() {
+ this(new ShardingWorkflowPlanningKernel());
+ }
+
+ /**
+ * Create sharding table rule workflow planning service with delegate.
+ *
+ * @param delegate sharding workflow planning service
+ */
+ public ShardingTableRuleWorkflowPlanningService(final ShardingWorkflowPlanningService delegate) {
+ this(delegate::planTableRule);
+ }
+
+ ShardingTableRuleWorkflowPlanningService(final ShardingWorkflowPlanningKernel kernel) {
+ this(kernel::planTableRule);
+ }
+
+ private ShardingTableRuleWorkflowPlanningService(final Planner planner) {
+ this.planner = planner;
+ }
+
+ /**
+ * Plan sharding table rule workflow.
+ *
+ * @param workflowSessionContext workflow session context
+ * @param queryFacade feature query facade
+ * @param sessionId MCP session ID
+ * @param request sharding table rule workflow request
+ * @return workflow context snapshot
+ */
+ public WorkflowContextSnapshot plan(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
+ final String sessionId, final ShardingTableRuleWorkflowRequest request) {
+ return planner.plan(workflowSessionContext, queryFacade, sessionId, request.toWorkflowRequest());
+ }
+
+ @FunctionalInterface
+ private interface Planner {
+
+ WorkflowContextSnapshot plan(WorkflowSessionContext workflowSessionContext, MCPFeatureQueryFacade queryFacade, String sessionId, ShardingWorkflowRequest request);
+ }
+}
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningKernel.java
similarity index 80%
copy from mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
copy to mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningKernel.java
index f62d49d1f40..90ce1713ff2 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningKernel.java
@@ -21,6 +21,8 @@ import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
import org.apache.shardingsphere.mcp.support.workflow.model.RuleArtifact;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
@@ -38,12 +40,13 @@ import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.function.BiFunction;
import java.util.function.Function;
/**
- * Sharding workflow planning service.
+ * Sharding workflow planning kernel.
*/
-public final class ShardingWorkflowPlanningService {
+final class ShardingWorkflowPlanningKernel {
private static final List<String> INTERACTION_STEPS = List.of(
"Confirm logical database and sharding intent",
@@ -59,15 +62,23 @@ public final class ShardingWorkflowPlanningService {
private final ShardingInspectionService inspectionService;
+ private final ShardingAlgorithmRecommendationService algorithmRecommendationService;
+
+ private final ShardingAlgorithmPropertyTemplateService algorithmPropertyTemplateService;
+
private final ShardingDistSQLPlanningService distSQLPlanningService;
- public ShardingWorkflowPlanningService() {
+ ShardingWorkflowPlanningKernel() {
inspectionService = new ShardingInspectionService();
+ algorithmRecommendationService = new ShardingAlgorithmRecommendationService();
+ algorithmPropertyTemplateService = new ShardingAlgorithmPropertyTemplateService();
distSQLPlanningService = new ShardingDistSQLPlanningService();
}
- ShardingWorkflowPlanningService(final ShardingInspectionService inspectionService, final ShardingDistSQLPlanningService distSQLPlanningService) {
+ ShardingWorkflowPlanningKernel(final ShardingInspectionService inspectionService, final ShardingDistSQLPlanningService distSQLPlanningService) {
this.inspectionService = inspectionService;
+ algorithmRecommendationService = new ShardingAlgorithmRecommendationService();
+ algorithmPropertyTemplateService = new ShardingAlgorithmPropertyTemplateService();
this.distSQLPlanningService = distSQLPlanningService;
}
@@ -84,7 +95,9 @@ public final class ShardingWorkflowPlanningService {
final String sessionId, final ShardingWorkflowRequest request) {
return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.TABLE_RULE_WORKFLOW_KIND,
"create", "Sharding table rule workflow plan.", actual -> !inspectionService.queryTableRule(queryFacade, actual.getDatabase(), actual.getTable()).isEmpty(),
- this::hasRequiredTableRuleInputs, actual -> distSQLPlanningService.planTableRule(actual, actual.getOperationType()));
+ this::hasRequiredTableRuleInputs, (actual, snapshot) -> planAlgorithms(queryFacade, actual,
+ shouldPlanShardingAlgorithm(actual), shouldPlanTableRuleKeyGenerator(actual), snapshot),
+ actual -> distSQLPlanningService.planTableRule(actual, actual.getOperationType()));
}
/**
@@ -100,7 +113,7 @@ public final class ShardingWorkflowPlanningService {
final String sessionId, final ShardingWorkflowRequest request) {
return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.TABLE_REFERENCE_WORKFLOW_KIND,
"create", "Sharding table reference rule workflow plan.", actual -> !inspectionService.queryTableReferenceRule(queryFacade, actual.getDatabase(), actual.getRuleName()).isEmpty(),
- this::hasRequiredReferenceRuleInputs, actual -> distSQLPlanningService.planTableReferenceRule(actual, actual.getOperationType()));
+ this::hasRequiredReferenceRuleInputs, (actual, snapshot) -> true, actual -> distSQLPlanningService.planTableReferenceRule(actual, actual.getOperationType()));
}
/**
@@ -118,6 +131,7 @@ public final class ShardingWorkflowPlanningService {
"create", "Default sharding strategy workflow plan.", actual -> containsDefaultStrategy(inspectionService.queryDefaultStrategy(queryFacade, actual.getDatabase()),
queryFacade.getDatabaseType(actual.getDatabase()), actual.getDefaultStrategyType()),
this::hasRequiredDefaultStrategyInputs,
+ (actual, snapshot) -> planAlgorithms(queryFacade, actual, shouldPlanDefaultStrategyAlgorithm(actual), false, snapshot),
actual -> distSQLPlanningService.planDefaultStrategy(actual, actual.getOperationType()));
}
@@ -134,7 +148,8 @@ public final class ShardingWorkflowPlanningService {
final String sessionId, final ShardingWorkflowRequest request) {
return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.KEY_GENERATOR_WORKFLOW_KIND,
"create", "Sharding key generator workflow plan.", actual -> !inspectionService.queryKeyGenerator(queryFacade, actual.getDatabase(), actual.getKeyGeneratorName()).isEmpty(),
- this::hasRequiredKeyGeneratorInputs, actual -> distSQLPlanningService.planKeyGenerator(actual, actual.getOperationType()));
+ this::hasRequiredKeyGeneratorInputs, (actual, snapshot) -> planAlgorithms(queryFacade, actual, false, true, snapshot),
+ actual -> distSQLPlanningService.planKeyGenerator(actual, actual.getOperationType()));
}
/**
@@ -151,7 +166,9 @@ public final class ShardingWorkflowPlanningService {
return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.KEY_GENERATE_STRATEGY_WORKFLOW_KIND,
"create", "Sharding key generate strategy workflow plan.",
actual -> !inspectionService.queryKeyGenerateStrategy(queryFacade, actual.getDatabase(), actual.getKeyGenerateStrategyName()).isEmpty(),
- this::hasRequiredKeyGenerateStrategyInputs, actual -> distSQLPlanningService.planKeyGenerateStrategy(actual, actual.getOperationType()));
+ this::hasRequiredKeyGenerateStrategyInputs, (actual, snapshot) -> planAlgorithms(queryFacade, actual, false,
+ shouldPlanKeyGenerateStrategyGenerator(actual), snapshot),
+ actual -> distSQLPlanningService.planKeyGenerateStrategy(actual, actual.getOperationType()));
}
/**
@@ -199,6 +216,7 @@ public final class ShardingWorkflowPlanningService {
final String sessionId, final ShardingWorkflowRequest request, final WorkflowKind workflowKind,
final String defaultOperationType, final String summary, final Function<ShardingWorkflowRequest, Boolean> existsSupplier,
final Function<ShardingWorkflowRequest, Boolean> requiredInputSupplier,
+ final BiFunction<ShardingWorkflowRequest, WorkflowContextSnapshot, Boolean> algorithmPlanSupplier,
final Function<ShardingWorkflowRequest, RuleArtifact> artifactSupplier) {
WorkflowContextSnapshot result = prepareSnapshot(workflowSessionContext, sessionId, request, workflowKind, defaultOperationType, summary);
ShardingWorkflowRequest mergedRequest = (ShardingWorkflowRequest) result.getRequest();
@@ -217,6 +235,9 @@ public final class ShardingWorkflowPlanningService {
if (!planningSupport.ensureLifecycleState(summary, result.getClarifiedIntent(), existsSupplier.apply(mergedRequest), result)) {
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
}
+ if (!algorithmPlanSupplier.apply(mergedRequest, result)) {
+ return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
+ }
queryFacade.getDatabaseType(mergedRequest.getDatabase());
result.getRuleArtifacts().add(artifactSupplier.apply(mergedRequest));
return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_REVIEW, WorkflowLifecycle.STATUS_PLANNED);
@@ -379,6 +400,62 @@ public final class ShardingWorkflowPlanningService {
databaseType, defaultStrategyType, WorkflowRuleValueUtils.getRuleValue(each, "name")) && !WorkflowRuleValueUtils.getRuleValue(each, "type").isEmpty());
}
+ private boolean planAlgorithms(final MCPFeatureQueryFacade queryFacade, final ShardingWorkflowRequest request, final boolean includeShardingAlgorithm,
+ final boolean includeKeyGenerator, final WorkflowContextSnapshot snapshot) {
+ if (!includeShardingAlgorithm && !includeKeyGenerator) {
+ return true;
+ }
+ List<AlgorithmCandidate> algorithmCandidates = algorithmRecommendationService.recommend(
+ request, inspectionService.queryAlgorithmPlugins(queryFacade), inspectionService.queryKeyGenerateAlgorithmPlugins(queryFacade),
+ includeShardingAlgorithm, includeKeyGenerator, snapshot.getIssues());
+ snapshot.getAlgorithmCandidates().addAll(algorithmCandidates);
+ applyRecommendedAlgorithms(request, algorithmCandidates);
+ return planningSupport.isReadyForArtifactPlanning(request, snapshot.getClarifiedIntent(), snapshot,
+ findPropertyRequirements(request, includeShardingAlgorithm, includeKeyGenerator),
+ "Please use sharding algorithms visible in the current Proxy and provide required properties.");
+ }
+
+ private void applyRecommendedAlgorithms(final ShardingWorkflowRequest request, final List<AlgorithmCandidate> algorithmCandidates) {
+ for (AlgorithmCandidate each : algorithmCandidates) {
+ if ("primary".equals(each.getAlgorithmRole())) {
+ request.setAlgorithmType(each.getAlgorithmType());
+ } else if ("key_generator".equals(each.getAlgorithmRole())) {
+ request.setKeyGeneratorType(each.getAlgorithmType());
+ }
+ }
+ }
+
+ private List<AlgorithmPropertyRequirement> findPropertyRequirements(final ShardingWorkflowRequest request, final boolean includeShardingAlgorithm,
+ final boolean includeKeyGenerator) {
+ if (includeShardingAlgorithm && includeKeyGenerator) {
+ return algorithmPropertyTemplateService.findRequirements(request.getAlgorithmType(), resolveKeyGeneratorType(request));
+ }
+ return includeShardingAlgorithm
+ ? algorithmPropertyTemplateService.findAlgorithmRequirements(request.getAlgorithmType())
+ : algorithmPropertyTemplateService.findKeyGeneratorRequirements(resolveKeyGeneratorType(request));
+ }
+
+ private boolean shouldPlanShardingAlgorithm(final ShardingWorkflowRequest request) {
+ return !WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType()) && !"none".equalsIgnoreCase(normalizeStrategyType(request));
+ }
+
+ private boolean shouldPlanDefaultStrategyAlgorithm(final ShardingWorkflowRequest request) {
+ return shouldPlanShardingAlgorithm(request);
+ }
+
+ private boolean shouldPlanTableRuleKeyGenerator(final ShardingWorkflowRequest request) {
+ return !WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType()) && !request.getKeyGenerateColumn().isEmpty()
+ && request.getKeyGeneratorName().isEmpty();
+ }
+
+ private boolean shouldPlanKeyGenerateStrategyGenerator(final ShardingWorkflowRequest request) {
+ return !WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType()) && request.getKeyGeneratorName().isEmpty();
+ }
+
+ private String resolveKeyGeneratorType(final ShardingWorkflowRequest request) {
+ return request.getKeyGeneratorType().isEmpty() ? request.getAlgorithmType() : request.getKeyGeneratorType();
+ }
+
private boolean isUnusedComponent(final MCPFeatureQueryFacade queryFacade, final ShardingWorkflowRequest request) {
return switch (normalizeComponentType(request.getComponentType())) {
case "algorithm" -> containsNamedRow(inspectionService.queryUnusedAlgorithms(queryFacade, request.getDatabase()),
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
index f62d49d1f40..2fa49337131 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningService.java
@@ -17,58 +17,52 @@
package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
-import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingDefaultStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGenerateStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGeneratorWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingRuleComponentCleanupWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableReferenceRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableRuleWorkflowRequest;
import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
-import org.apache.shardingsphere.mcp.support.workflow.model.ClarifiedIntent;
-import org.apache.shardingsphere.mcp.support.workflow.model.RuleArtifact;
import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
-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.WorkflowKind;
-import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
-import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningSupport;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRuleValueUtils;
-import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.function.Function;
/**
* Sharding workflow planning service.
*/
public final class ShardingWorkflowPlanningService {
- private static final List<String> INTERACTION_STEPS = List.of(
- "Confirm logical database and sharding intent",
- "Inspect DistSQL-visible sharding state",
- "Generate reviewable sharding DistSQL artifact",
- "Review artifact and choose execution mode",
- "Execute or export artifact",
- "Validate DistSQL-visible state");
+ private final ShardingTableRuleWorkflowPlanningService tableRulePlanningService;
+
+ private final ShardingTableReferenceRuleWorkflowPlanningService tableReferenceRulePlanningService;
- private static final List<String> VALIDATION_LAYERS = List.of("rules", "algorithms", "key-generators", "auditors");
+ private final ShardingDefaultStrategyWorkflowPlanningService defaultStrategyPlanningService;
- private final WorkflowPlanningSupport planningSupport = new WorkflowPlanningSupport();
+ private final ShardingKeyGeneratorWorkflowPlanningService keyGeneratorPlanningService;
- private final ShardingInspectionService inspectionService;
+ private final ShardingKeyGenerateStrategyWorkflowPlanningService keyGenerateStrategyPlanningService;
- private final ShardingDistSQLPlanningService distSQLPlanningService;
+ private final ShardingRuleComponentCleanupWorkflowPlanningService ruleComponentCleanupPlanningService;
+ /**
+ * Create sharding workflow planning service.
+ */
public ShardingWorkflowPlanningService() {
- inspectionService = new ShardingInspectionService();
- distSQLPlanningService = new ShardingDistSQLPlanningService();
+ this(new ShardingWorkflowPlanningKernel());
}
ShardingWorkflowPlanningService(final ShardingInspectionService inspectionService, final ShardingDistSQLPlanningService distSQLPlanningService) {
- this.inspectionService = inspectionService;
- this.distSQLPlanningService = distSQLPlanningService;
+ this(new ShardingWorkflowPlanningKernel(inspectionService, distSQLPlanningService));
+ }
+
+ private ShardingWorkflowPlanningService(final ShardingWorkflowPlanningKernel kernel) {
+ tableRulePlanningService = new ShardingTableRuleWorkflowPlanningService(kernel);
+ tableReferenceRulePlanningService = new ShardingTableReferenceRuleWorkflowPlanningService(kernel);
+ defaultStrategyPlanningService = new ShardingDefaultStrategyWorkflowPlanningService(kernel);
+ keyGeneratorPlanningService = new ShardingKeyGeneratorWorkflowPlanningService(kernel);
+ keyGenerateStrategyPlanningService = new ShardingKeyGenerateStrategyWorkflowPlanningService(kernel);
+ ruleComponentCleanupPlanningService = new ShardingRuleComponentCleanupWorkflowPlanningService(kernel);
}
/**
@@ -82,9 +76,7 @@ public final class ShardingWorkflowPlanningService {
*/
public WorkflowContextSnapshot planTableRule(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
final String sessionId, final ShardingWorkflowRequest request) {
- return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.TABLE_RULE_WORKFLOW_KIND,
- "create", "Sharding table rule workflow plan.", actual -> !inspectionService.queryTableRule(queryFacade, actual.getDatabase(), actual.getTable()).isEmpty(),
- this::hasRequiredTableRuleInputs, actual -> distSQLPlanningService.planTableRule(actual, actual.getOperationType()));
+ return tableRulePlanningService.plan(workflowSessionContext, queryFacade, sessionId, new ShardingTableRuleWorkflowRequest(request));
}
/**
@@ -98,9 +90,7 @@ public final class ShardingWorkflowPlanningService {
*/
public WorkflowContextSnapshot planTableReferenceRule(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
final String sessionId, final ShardingWorkflowRequest request) {
- return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.TABLE_REFERENCE_WORKFLOW_KIND,
- "create", "Sharding table reference rule workflow plan.", actual -> !inspectionService.queryTableReferenceRule(queryFacade, actual.getDatabase(), actual.getRuleName()).isEmpty(),
- this::hasRequiredReferenceRuleInputs, actual -> distSQLPlanningService.planTableReferenceRule(actual, actual.getOperationType()));
+ return tableReferenceRulePlanningService.plan(workflowSessionContext, queryFacade, sessionId, new ShardingTableReferenceRuleWorkflowRequest(request));
}
/**
@@ -114,11 +104,7 @@ public final class ShardingWorkflowPlanningService {
*/
public WorkflowContextSnapshot planDefaultStrategy(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
final String sessionId, final ShardingWorkflowRequest request) {
- return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.DEFAULT_STRATEGY_WORKFLOW_KIND,
- "create", "Default sharding strategy workflow plan.", actual -> containsDefaultStrategy(inspectionService.queryDefaultStrategy(queryFacade, actual.getDatabase()),
- queryFacade.getDatabaseType(actual.getDatabase()), actual.getDefaultStrategyType()),
- this::hasRequiredDefaultStrategyInputs,
- actual -> distSQLPlanningService.planDefaultStrategy(actual, actual.getOperationType()));
+ return defaultStrategyPlanningService.plan(workflowSessionContext, queryFacade, sessionId, new ShardingDefaultStrategyWorkflowRequest(request));
}
/**
@@ -132,9 +118,7 @@ public final class ShardingWorkflowPlanningService {
*/
public WorkflowContextSnapshot planKeyGenerator(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
final String sessionId, final ShardingWorkflowRequest request) {
- return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.KEY_GENERATOR_WORKFLOW_KIND,
- "create", "Sharding key generator workflow plan.", actual -> !inspectionService.queryKeyGenerator(queryFacade, actual.getDatabase(), actual.getKeyGeneratorName()).isEmpty(),
- this::hasRequiredKeyGeneratorInputs, actual -> distSQLPlanningService.planKeyGenerator(actual, actual.getOperationType()));
+ return keyGeneratorPlanningService.plan(workflowSessionContext, queryFacade, sessionId, new ShardingKeyGeneratorWorkflowRequest(request));
}
/**
@@ -148,10 +132,7 @@ public final class ShardingWorkflowPlanningService {
*/
public WorkflowContextSnapshot planKeyGenerateStrategy(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
final String sessionId, final ShardingWorkflowRequest request) {
- return planLifecycleWorkflow(workflowSessionContext, queryFacade, sessionId, request, ShardingFeatureDefinition.KEY_GENERATE_STRATEGY_WORKFLOW_KIND,
- "create", "Sharding key generate strategy workflow plan.",
- actual -> !inspectionService.queryKeyGenerateStrategy(queryFacade, actual.getDatabase(), actual.getKeyGenerateStrategyName()).isEmpty(),
- this::hasRequiredKeyGenerateStrategyInputs, actual -> distSQLPlanningService.planKeyGenerateStrategy(actual, actual.getOperationType()));
+ return keyGenerateStrategyPlanningService.plan(workflowSessionContext, queryFacade, sessionId, new ShardingKeyGenerateStrategyWorkflowRequest(request));
}
/**
@@ -165,262 +146,6 @@ public final class ShardingWorkflowPlanningService {
*/
public WorkflowContextSnapshot planComponentCleanup(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
final String sessionId, final ShardingWorkflowRequest request) {
- WorkflowContextSnapshot result = prepareSnapshot(workflowSessionContext, sessionId, request, ShardingFeatureDefinition.COMPONENT_CLEANUP_WORKFLOW_KIND,
- WorkflowLifecycle.OPERATION_DROP, "Sharding rule component cleanup workflow plan.");
- ShardingWorkflowRequest mergedRequest = (ShardingWorkflowRequest) result.getRequest();
- if (!hasDatabase(mergedRequest, result) || !hasRequiredCleanupInputs(mergedRequest, result)) {
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
- }
- if (!ensureCleanupDropOnly(mergedRequest, result)) {
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
- }
- if (!isUnusedComponent(queryFacade, mergedRequest) || !queryUsedBy(queryFacade, mergedRequest).isEmpty()) {
- result.getIssues().add(new WorkflowIssue(WorkflowIssueCode.RULE_STATE_MISMATCH, "error", "discovering",
- "Sharding component is still used or is not present in unused DistSQL-visible state.",
- "Inspect unused and used-by resources before cleanup.", false, Map.of()));
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
- }
- result.getRuleArtifacts().add(distSQLPlanningService.planComponentCleanup(mergedRequest));
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_REVIEW, WorkflowLifecycle.STATUS_PLANNED);
- }
-
- private boolean ensureCleanupDropOnly(final ShardingWorkflowRequest request, final WorkflowContextSnapshot snapshot) {
- if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType())) {
- return true;
- }
- snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.WORKFLOW_STATUS_INVALID, "error", "intaking",
- "Sharding component cleanup only supports dropping unused components.", "Use operation_type=drop or omit operation_type for cleanup.", false,
- Map.of("operation_type", request.getOperationType())));
- snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
- return false;
- }
-
- private WorkflowContextSnapshot planLifecycleWorkflow(final WorkflowSessionContext workflowSessionContext, final MCPFeatureQueryFacade queryFacade,
- final String sessionId, final ShardingWorkflowRequest request, final WorkflowKind workflowKind,
- final String defaultOperationType, final String summary, final Function<ShardingWorkflowRequest, Boolean> existsSupplier,
- final Function<ShardingWorkflowRequest, Boolean> requiredInputSupplier,
- final Function<ShardingWorkflowRequest, RuleArtifact> artifactSupplier) {
- WorkflowContextSnapshot result = prepareSnapshot(workflowSessionContext, sessionId, request, workflowKind, defaultOperationType, summary);
- ShardingWorkflowRequest mergedRequest = (ShardingWorkflowRequest) result.getRequest();
- if (!hasDatabase(mergedRequest, result)) {
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
- }
- if (!ensureIdentifiers(mergedRequest, result)) {
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, result.getStatus());
- }
- if (!requiredInputSupplier.apply(mergedRequest)) {
- result.getClarifiedIntent().getClarificationMessages().add(
- mergedRequest.getFieldSemantics().isEmpty() ? "Please provide the missing sharding planning inputs." : mergedRequest.getFieldSemantics());
- result.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
- }
- if (!planningSupport.ensureLifecycleState(summary, result.getClarifiedIntent(), existsSupplier.apply(mergedRequest), result)) {
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_FAILED, WorkflowLifecycle.STATUS_FAILED);
- }
- queryFacade.getDatabaseType(mergedRequest.getDatabase());
- result.getRuleArtifacts().add(artifactSupplier.apply(mergedRequest));
- return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_REVIEW, WorkflowLifecycle.STATUS_PLANNED);
- }
-
- private WorkflowContextSnapshot prepareSnapshot(final WorkflowSessionContext workflowSessionContext, final String sessionId, final ShardingWorkflowRequest request,
- final WorkflowKind workflowKind, final String defaultOperationType, final String summary) {
- WorkflowContextSnapshot result = workflowSessionContext.getOrCreate(sessionId, request.getPlanId());
- ShardingWorkflowRequest mergedRequest = ShardingWorkflowRequest.merge(result.getRequest(), request);
- ClarifiedIntent clarifiedIntent = resolveIntent(mergedRequest, defaultOperationType);
- planningSupport.prepareSnapshot(result, workflowKind, mergedRequest, null, clarifiedIntent, summary, INTERACTION_STEPS, VALIDATION_LAYERS);
- planningSupport.applyResolvedIntent(mergedRequest, clarifiedIntent);
- return result;
- }
-
- private ClarifiedIntent resolveIntent(final WorkflowRequest request, final String defaultOperationType) {
- ClarifiedIntent result = new ClarifiedIntent();
- result.setOperationType(request.getOperationType().isEmpty() ? defaultOperationType : request.getOperationType());
- result.setFieldSemantics("DistSQL-visible sharding rule fields only.");
- return result;
- }
-
- private boolean hasDatabase(final ShardingWorkflowRequest request, final WorkflowContextSnapshot snapshot) {
- if (!request.getDatabase().isEmpty()) {
- return true;
- }
- snapshot.getClarifiedIntent().getClarificationMessages().add("Please provide logical database first.");
- snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
- return false;
- }
-
- private boolean ensureIdentifiers(final ShardingWorkflowRequest request, final WorkflowContextSnapshot snapshot) {
- if (WorkflowSQLUtils.isSupportedIdentifier(request.getDatabase()) && WorkflowSQLUtils.isSupportedIdentifier(request.getTable())
- && WorkflowSQLUtils.isSupportedIdentifier(request.getColumn()) && WorkflowSQLUtils.isSupportedIdentifier(request.getRuleName())
- && WorkflowSQLUtils.isSupportedIdentifier(request.getKeyGeneratorName()) && WorkflowSQLUtils.isSupportedIdentifier(request.getKeyGenerateStrategyName())
- && areSupportedIdentifiers(splitCsv(request.getShardingColumns()))) {
- return true;
- }
- snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
- "A sharding identifier contains unsupported characters.", "Use reviewable DistSQL identifiers only.", false, Map.of()));
- snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
- return false;
- }
-
- private boolean hasRequiredTableRuleInputs(final ShardingWorkflowRequest request) {
- if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType())) {
- return require(request, request.getTable(), "Please provide target logical table.");
- }
- if (!require(request, request.getTable(), "Please provide target logical table.")
- || !require(request, request.getDataNodes().isEmpty() ? request.getStorageUnits() : request.getDataNodes(), "Please provide data nodes or storage units.")) {
- return false;
- }
- return (request.getStorageUnits().isEmpty() ? hasRequiredStrategyInputs(request) : hasRequiredAutoTableRuleInputs(request))
- && hasRequiredTableRuleKeyGenerateInputs(request);
- }
-
- private boolean hasRequiredAutoTableRuleInputs(final ShardingWorkflowRequest request) {
- return require(request, request.getColumn(), "Please provide auto table sharding column.")
- && require(request, request.getAlgorithmType(), "Please provide auto table sharding algorithm type.");
- }
-
- private boolean hasRequiredTableRuleKeyGenerateInputs(final ShardingWorkflowRequest request) {
- if (request.getKeyGenerateColumn().isEmpty() || !request.getKeyGeneratorName().isEmpty()) {
- return true;
- }
- return require(request, request.getKeyGeneratorType(), "Please provide key generator type or key generator name for key generate strategy.");
- }
-
- private boolean hasRequiredReferenceRuleInputs(final ShardingWorkflowRequest request) {
- if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType())) {
- return require(request, request.getRuleName(), "Please provide table reference rule name.");
- }
- return require(request, request.getRuleName(), "Please provide table reference rule name.")
- && require(request, request.getReferenceTables().isEmpty() ? "" : "ok", "Please provide reference tables.");
- }
-
- private boolean hasRequiredDefaultStrategyInputs(final ShardingWorkflowRequest request) {
- if (!require(request, request.getDefaultStrategyType(), "Please provide DATABASE or TABLE default strategy type.")) {
- return false;
- }
- if (!isDefaultStrategyType(request.getDefaultStrategyType())) {
- request.setFieldSemantics("Please provide default_strategy_type as DATABASE or TABLE.");
- return false;
- }
- if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType()) || "none".equalsIgnoreCase(request.getStrategyType())) {
- return true;
- }
- return hasRequiredStrategyInputs(request);
- }
-
- private boolean isDefaultStrategyType(final String defaultStrategyType) {
- return "DATABASE".equalsIgnoreCase(defaultStrategyType) || "TABLE".equalsIgnoreCase(defaultStrategyType);
- }
-
- private boolean hasRequiredStrategyInputs(final ShardingWorkflowRequest request) {
- switch (normalizeStrategyType(request)) {
- case "standard":
- return require(request, request.getColumn(), "Please provide sharding column.")
- && require(request, request.getAlgorithmType(), "Please provide sharding algorithm type.");
- case "complex":
- return hasRequiredComplexStrategyInputs(request);
- case "hint":
- return require(request, request.getAlgorithmType(), "Please provide sharding algorithm type.");
- case "none":
- return true;
- default:
- request.setFieldSemantics("Please provide strategy_type as standard, complex, hint, or none.");
- return false;
- }
- }
-
- private boolean hasRequiredComplexStrategyInputs(final ShardingWorkflowRequest request) {
- if (!require(request, request.getShardingColumns(), "Please provide at least two sharding columns for complex strategy.")) {
- return false;
- }
- if (splitCsv(request.getShardingColumns()).size() < 2) {
- request.setFieldSemantics("Please provide at least two sharding columns for complex strategy.");
- return false;
- }
- return require(request, request.getAlgorithmType(), "Please provide sharding algorithm type.");
- }
-
- private boolean hasRequiredKeyGeneratorInputs(final ShardingWorkflowRequest request) {
- if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType())) {
- return require(request, request.getKeyGeneratorName(), "Please provide key generator name.");
- }
- return require(request, request.getKeyGeneratorName(), "Please provide key generator name.")
- && require(request, request.getKeyGeneratorType().isEmpty() ? request.getAlgorithmType() : request.getKeyGeneratorType(), "Please provide key generator type.");
- }
-
- private boolean hasRequiredKeyGenerateStrategyInputs(final ShardingWorkflowRequest request) {
- if (WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(request.getOperationType())) {
- return require(request, request.getKeyGenerateStrategyName(), "Please provide key generate strategy name.");
- }
- return require(request, request.getKeyGenerateStrategyName(), "Please provide key generate strategy name.")
- && require(request, request.getSequenceName().isEmpty() ? request.getTable() : request.getSequenceName(), "Please provide table or sequence.")
- && require(request, request.getSequenceName().isEmpty() ? request.getColumn() : "ok", "Please provide key generate column.")
- && require(request, request.getKeyGeneratorName().isEmpty() ? request.getAlgorithmType() : request.getKeyGeneratorName(), "Please provide generator or key algorithm type.");
- }
-
- private boolean hasRequiredCleanupInputs(final ShardingWorkflowRequest request, final WorkflowContextSnapshot snapshot) {
- boolean result = !request.getComponentType().isEmpty() && !request.getComponentName().isEmpty();
- if (!result) {
- snapshot.getClarifiedIntent().getClarificationMessages().add("Please provide component type and component name.");
- snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
- }
- return result;
- }
-
- private boolean require(final ShardingWorkflowRequest request, final String value, final String message) {
- if (!value.isEmpty()) {
- return true;
- }
- request.setFieldSemantics(message);
- return false;
- }
-
- private boolean containsDefaultStrategy(final List<Map<String, Object>> rows, final String databaseType, final String defaultStrategyType) {
- return rows.stream().anyMatch(each -> WorkflowSQLUtils.isSameIdentifier(
- databaseType, defaultStrategyType, WorkflowRuleValueUtils.getRuleValue(each, "name")) && !WorkflowRuleValueUtils.getRuleValue(each, "type").isEmpty());
- }
-
- private boolean isUnusedComponent(final MCPFeatureQueryFacade queryFacade, final ShardingWorkflowRequest request) {
- return switch (normalizeComponentType(request.getComponentType())) {
- case "algorithm" -> containsNamedRow(inspectionService.queryUnusedAlgorithms(queryFacade, request.getDatabase()),
- "name", request.getComponentName(), queryFacade.getDatabaseType(request.getDatabase()));
- case "key-generator" -> containsNamedRow(inspectionService.queryUnusedKeyGenerators(queryFacade, request.getDatabase()), "name", request.getComponentName(),
- queryFacade.getDatabaseType(request.getDatabase()));
- case "auditor" -> containsNamedRow(inspectionService.queryUnusedAuditors(queryFacade, request.getDatabase()),
- "name", request.getComponentName(), queryFacade.getDatabaseType(request.getDatabase()));
- default -> false;
- };
- }
-
- private List<Map<String, Object>> queryUsedBy(final MCPFeatureQueryFacade queryFacade, final ShardingWorkflowRequest request) {
- switch (normalizeComponentType(request.getComponentType())) {
- case "algorithm":
- return inspectionService.queryTableRulesUsedAlgorithm(queryFacade, request.getDatabase(), request.getComponentName());
- case "key-generator":
- return inspectionService.queryTableRulesUsedKeyGenerator(queryFacade, request.getDatabase(), request.getComponentName());
- case "auditor":
- return inspectionService.queryTableRulesUsedAuditor(queryFacade, request.getDatabase(), request.getComponentName());
- default:
- return List.of(Map.of("unsupported_component_type", request.getComponentType()));
- }
- }
-
- private boolean containsNamedRow(final Collection<Map<String, Object>> rows, final String fieldName, final String expected, final String databaseType) {
- return rows.stream().anyMatch(each -> WorkflowSQLUtils.isSameIdentifier(databaseType, expected, WorkflowRuleValueUtils.getRuleValue(each, fieldName)));
- }
-
- private boolean areSupportedIdentifiers(final Collection<String> identifiers) {
- return identifiers.stream().allMatch(WorkflowSQLUtils::isSupportedIdentifier);
- }
-
- private List<String> splitCsv(final String value) {
- return Arrays.stream(value.split(",")).map(String::trim).filter(each -> !each.isEmpty()).toList();
- }
-
- private String normalizeStrategyType(final ShardingWorkflowRequest request) {
- return request.getStrategyType().isEmpty() ? "standard" : request.getStrategyType().trim().toLowerCase(Locale.ENGLISH);
- }
-
- private String normalizeComponentType(final String componentType) {
- return componentType.trim().toLowerCase(Locale.ENGLISH).replace('_', '-');
+ return ruleComponentCleanupPlanningService.plan(workflowSessionContext, queryFacade, sessionId, new ShardingRuleComponentCleanupWorkflowRequest(request));
}
}
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml
index e7021e3b4ba..35bd3bd44c9 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-sharding.yaml
@@ -335,6 +335,10 @@ prompts:
title: Sharding Algorithm
description: "Optional sharding algorithm type."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a sharding table rule plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_sharding_table_rule
@@ -366,6 +370,10 @@ prompts:
title: Operation Type
description: "create, alter, or drop."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue this sharding plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_sharding_table_reference_rule
@@ -398,6 +406,10 @@ prompts:
title: Sharding Algorithm
description: "Optional default sharding algorithm type."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a default sharding strategy plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_sharding_default_strategy
@@ -431,6 +443,10 @@ prompts:
title: Key Generator Algorithm
description: "Optional key generator algorithm type."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a sharding key generator plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_sharding_key_generator
@@ -464,6 +480,10 @@ prompts:
title: Key Generator Algorithm
description: "Optional key generator algorithm type."
required: false
+ - name: plan_id
+ title: Workflow Plan ID
+ description: "Optional current-session workflow plan identifier used to continue a sharding key generate strategy plan."
+ required: false
meta:
org.apache.shardingsphere/related-tools:
- database_gateway_plan_sharding_key_generate_strategy
@@ -658,6 +678,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning sharding table rule changes."
+ - from: database_gateway_plan_sharding_table_rule
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed sharding table rule workflow after user approval."
- from: database_gateway_plan_sharding_table_reference_rule
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -665,6 +692,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning table reference rule changes."
+ - from: database_gateway_plan_sharding_table_reference_rule
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed table reference rule workflow after user approval."
- from: database_gateway_plan_sharding_default_strategy
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -672,6 +706,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning default sharding strategy changes."
+ - from: database_gateway_plan_sharding_default_strategy
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed default sharding strategy workflow after user approval."
- from: database_gateway_plan_sharding_key_generator
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -679,6 +720,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning key generator changes."
+ - from: database_gateway_plan_sharding_key_generator
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed sharding key generator workflow after user approval."
- from: database_gateway_plan_sharding_key_generate_strategy
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -686,6 +734,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning key generate strategy changes."
+ - from: database_gateway_plan_sharding_key_generate_strategy
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed key generate strategy workflow after user approval."
- from: database_gateway_plan_sharding_rule_component_cleanup
to: shardingsphere://workflows/{plan_id}
requiredArguments:
@@ -693,6 +748,13 @@ resourceNavigation:
carriedArguments:
- plan_id
description: "Read the persisted workflow plan after planning sharding component cleanup."
+ - from: database_gateway_plan_sharding_rule_component_cleanup
+ to: database_gateway_apply_workflow
+ requiredArguments:
+ - plan_id
+ carriedArguments:
+ - plan_id
+ description: "Apply the reviewed sharding component cleanup workflow after user approval."
tools:
- name: database_gateway_plan_sharding_table_rule
title: Plan Sharding Table Rule
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-default-strategy.md b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-default-strategy.md
index 7045c1a44e8..994e094255d 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-default-strategy.md
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-default-strategy.md
@@ -1,3 +1,9 @@
# Plan Sharding Default Strategy
+Inputs:
+- database: {{database}}
+- operation_type: {{operation_type}}
+- algorithm_type: {{algorithm_type}}
+- plan_id: {{plan_id}}
+
Plan only ShardingSphere default sharding database or table strategy DistSQL. Require DATABASE or TABLE, strategy type, and algorithm details unless dropping or using NONE.
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generate-strategy.md b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generate-strategy.md
index 0c3f7d31a94..adb9553a2ed 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generate-strategy.md
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generate-strategy.md
@@ -1,3 +1,9 @@
# Plan Sharding Key Generate Strategy
+Inputs:
+- database: {{database}}
+- operation_type: {{operation_type}}
+- key_generator_type: {{key_generator_type}}
+- plan_id: {{plan_id}}
+
Plan only ShardingSphere key generate strategy DistSQL. Require strategy name, table and column or sequence, and either an existing generator name or key generator algorithm details.
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generator.md b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generator.md
index a343da954e2..4019f61d9f0 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generator.md
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-key-generator.md
@@ -1,3 +1,9 @@
# Plan Sharding Key Generator
+Inputs:
+- database: {{database}}
+- operation_type: {{operation_type}}
+- key_generator_type: {{key_generator_type}}
+- plan_id: {{plan_id}}
+
Plan only ShardingSphere sharding key generator DistSQL. Require key generator name and key generator algorithm type before create or alter.
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-rule-component-cleanup.md b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-rule-component-cleanup.md
index fad3920353c..7b4290acfad 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-rule-component-cleanup.md
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-rule-component-cleanup.md
@@ -1,3 +1,8 @@
# Plan Sharding Rule Component Cleanup
+Inputs:
+- database: {{database}}
+- operation_type: {{operation_type}}
+- plan_id: {{plan_id}}
+
Plan cleanup only with `DROP SHARDING ALGORITHM`, `DROP SHARDING KEY GENERATOR`, or `DROP SHARDING AUDITOR`. First read unused and used-by resources; do not plan cleanup when the component is still referenced.
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-reference-rule.md b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-reference-rule.md
index 8783c40c13e..8fcc25b1f32 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-reference-rule.md
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-reference-rule.md
@@ -1,3 +1,8 @@
# Plan Sharding Table Reference Rule
+Inputs:
+- database: {{database}}
+- operation_type: {{operation_type}}
+- plan_id: {{plan_id}}
+
Plan only ShardingSphere sharding table reference rule DistSQL. Require the logical database, rule name, and explicit reference tables. Do not emit unrelated DDL or storage-unit changes.
diff --git a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-rule.md b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-rule.md
index 4fcbb0c95b5..e3db67b9a02 100644
--- a/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-rule.md
+++ b/mcp/features/sharding/src/main/resources/META-INF/shardingsphere-mcp/prompts/plan-sharding-table-rule.md
@@ -1,3 +1,9 @@
# Plan Sharding Table Rule
+Inputs:
+- database: {{database}}
+- operation_type: {{operation_type}}
+- algorithm_type: {{algorithm_type}}
+- plan_id: {{plan_id}}
+
Plan only ShardingSphere sharding table rule DistSQL. Do not create physical databases, physical tables, indexes, storage units, migrations, backfills, or data probes. If data nodes, table, standard sharding column, complex sharding columns, strategy type, or algorithm details are missing for the selected strategy, ask for them before planning.
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProviderTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProviderTest.java
index c6acc2088ab..be0ca356066 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProviderTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/ShardingMCPHandlerProviderTest.java
@@ -20,6 +20,10 @@ package org.apache.shardingsphere.mcp.feature.sharding;
import org.apache.shardingsphere.mcp.api.MCPHandlerProvider;
import org.apache.shardingsphere.mcp.api.resource.MCPResourceHandler;
import org.apache.shardingsphere.mcp.api.tool.MCPToolHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingAlgorithmResourceHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingGovernanceResourceHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingStrategyResourceHandler;
+import org.apache.shardingsphere.mcp.feature.sharding.resource.handler.ShardingTableResourceHandler;
import org.apache.shardingsphere.mcp.support.workflow.spi.WorkflowRuntimeDefinition;
import org.junit.jupiter.api.Test;
@@ -39,6 +43,10 @@ class ShardingMCPHandlerProviderTest {
assertThat(actual.size(), is(22));
assertTrue(actual.stream().map(MCPResourceHandler::getResourceUriTemplate).toList().contains(ShardingFeatureDefinition.TABLE_RULES_RESOURCE_URI));
assertTrue(actual.stream().map(MCPResourceHandler::getResourceUriTemplate).toList().contains(ShardingFeatureDefinition.UNUSED_AUDITORS_RESOURCE_URI));
+ assertTrue(actual.stream().anyMatch(each -> each instanceof ShardingAlgorithmResourceHandler));
+ assertTrue(actual.stream().anyMatch(each -> each instanceof ShardingTableResourceHandler));
+ assertTrue(actual.stream().anyMatch(each -> each instanceof ShardingStrategyResourceHandler));
+ assertTrue(actual.stream().anyMatch(each -> each instanceof ShardingGovernanceResourceHandler));
}
@Test
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/descriptor/ShardingToolDescriptorValidatorTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/descriptor/ShardingToolDescriptorValidatorTest.java
index aaa95007610..15dfea1451c 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/descriptor/ShardingToolDescriptorValidatorTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/descriptor/ShardingToolDescriptorValidatorTest.java
@@ -21,6 +21,7 @@ import org.apache.shardingsphere.mcp.api.tool.descriptor.MCPToolDescriptor;
import org.apache.shardingsphere.mcp.feature.sharding.ShardingFeatureDefinition;
import org.apache.shardingsphere.mcp.support.descriptor.MCPCompletionTargetDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPDescriptorCatalogIndex;
+import org.apache.shardingsphere.mcp.support.descriptor.MCPResourceNavigationDescriptor;
import org.apache.shardingsphere.mcp.support.descriptor.MCPToolDescriptorValidator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -139,6 +140,27 @@ class ShardingToolDescriptorValidatorTest {
}
}
+ @Test
+ void assertPlanToolsNavigateToWorkflowAndApply() {
+ for (String each : List.of(ShardingFeatureDefinition.PLAN_TABLE_RULE_TOOL_NAME, ShardingFeatureDefinition.PLAN_TABLE_REFERENCE_TOOL_NAME,
+ ShardingFeatureDefinition.PLAN_DEFAULT_STRATEGY_TOOL_NAME, ShardingFeatureDefinition.PLAN_KEY_GENERATOR_TOOL_NAME,
+ ShardingFeatureDefinition.PLAN_KEY_GENERATE_STRATEGY_TOOL_NAME, ShardingFeatureDefinition.PLAN_COMPONENT_CLEANUP_TOOL_NAME)) {
+ assertPlanToolNavigation(each);
+ }
+ }
+
+ @Test
+ void assertAlgorithmResourceNavigationCarriesAlgorithmTypes() {
+ assertThat(findNavigation(ShardingFeatureDefinition.ALGORITHM_PLUGINS_RESOURCE_URI,
+ ShardingFeatureDefinition.PLAN_TABLE_RULE_TOOL_NAME).getCarriedArguments(), is(List.of("algorithm_type")));
+ assertThat(findNavigation(ShardingFeatureDefinition.ALGORITHM_PLUGINS_RESOURCE_URI,
+ ShardingFeatureDefinition.PLAN_DEFAULT_STRATEGY_TOOL_NAME).getCarriedArguments(), is(List.of("algorithm_type")));
+ assertThat(findNavigation(ShardingFeatureDefinition.KEY_GENERATE_ALGORITHM_PLUGINS_RESOURCE_URI,
+ ShardingFeatureDefinition.PLAN_KEY_GENERATOR_TOOL_NAME).getCarriedArguments(), is(List.of("key_generator_type")));
+ assertThat(findNavigation(ShardingFeatureDefinition.KEY_GENERATE_ALGORITHM_PLUGINS_RESOURCE_URI,
+ ShardingFeatureDefinition.PLAN_KEY_GENERATE_STRATEGY_TOOL_NAME).getCarriedArguments(), is(List.of("key_generator_type")));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("requiredOutputFields")
@SuppressWarnings("unchecked")
@@ -195,4 +217,20 @@ class ShardingToolDescriptorValidatorTest {
return MCPDescriptorCatalogIndex.getCompletionTargetDescriptors().stream()
.anyMatch(each -> referenceType.equals(each.getReferenceType()) && reference.equals(each.getReference()));
}
+
+ private void assertPlanToolNavigation(final String toolName) {
+ assertNavigation(toolName, "shardingsphere://workflows/{plan_id}");
+ assertNavigation(toolName, "database_gateway_apply_workflow");
+ }
+
+ private void assertNavigation(final String from, final String to) {
+ MCPResourceNavigationDescriptor actual = findNavigation(from, to);
+ assertThat(actual.getRequiredArguments(), is(List.of("plan_id")));
+ assertThat(actual.getCarriedArguments(), is(List.of("plan_id")));
+ }
+
+ private MCPResourceNavigationDescriptor findNavigation(final String from, final String to) {
+ return MCPDescriptorCatalogIndex.getResourceNavigationDescriptors(from).stream()
+ .filter(each -> to.equals(each.getTo())).findFirst().orElseThrow();
+ }
}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingAlgorithmResourceHandlerTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingAlgorithmResourceHandlerTest.java
new file mode 100644
index 00000000000..5bf4b632ef9
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingAlgorithmResourceHandlerTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ShardingAlgorithmResourceHandlerTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertHandleArguments")
+ void assertHandle(final String name, final ShardingAlgorithmResourceHandler handler, final MCPUriVariables uriVariables, final List<Map<String, Object>> rows,
+ final String expectedSelfUri) {
+ MCPDatabaseHandlerContext databaseContext = mock(MCPDatabaseHandlerContext.class);
+ when(databaseContext.getQueryFacade()).thenReturn(mock(MCPFeatureQueryFacade.class));
+ MCPResponse actual = handler.handle(databaseContext, uriVariables);
+ assertThat(((List<?>) actual.toPayload().get("items")).size(), is(rows.size()));
+ assertThat(actual.toPayload().get("self_uri"), is(expectedSelfUri));
+ }
+
+ private static Stream<Arguments> assertHandleArguments() {
+ List<Map<String, Object>> rows = List.of(Map.of("name", "inline_algorithm"));
+ ShardingInspectionService service = createInspectionService(rows);
+ return Stream.of(
+ createArguments("algorithm plugins", "shardingsphere://features/sharding/algorithm-plugins",
+ ShardingAlgorithmResourceHandler.ResourceKind.ALGORITHM_PLUGINS, Map.of(), rows,
+ "shardingsphere://features/sharding/algorithm-plugins", service),
+ createArguments("key generate algorithm plugins", "shardingsphere://features/sharding/key-generate-algorithm-plugins",
+ ShardingAlgorithmResourceHandler.ResourceKind.KEY_GENERATE_ALGORITHM_PLUGINS, Map.of(), rows,
+ "shardingsphere://features/sharding/key-generate-algorithm-plugins", service),
+ createArguments("algorithms", "shardingsphere://features/sharding/databases/{database}/algorithms",
+ ShardingAlgorithmResourceHandler.ResourceKind.ALGORITHMS, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/algorithms", service),
+ createArguments("unused algorithms", "shardingsphere://features/sharding/databases/{database}/unused-algorithms",
+ ShardingAlgorithmResourceHandler.ResourceKind.UNUSED_ALGORITHMS, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/unused-algorithms", service),
+ createArguments("algorithm used table rules", "shardingsphere://features/sharding/databases/{database}/algorithms/{algorithm}/table-rules",
+ ShardingAlgorithmResourceHandler.ResourceKind.ALGORITHM_USED_TABLE_RULES, Map.of("database", "logic_db", "algorithm", "inline_algorithm"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/algorithms/inline_algorithm/table-rules", service));
+ }
+
+ private static Arguments createArguments(final String name, final String resourceUriTemplate, final ShardingAlgorithmResourceHandler.ResourceKind resourceKind,
+ final Map<String, String> uriVariables, final List<Map<String, Object>> rows, final String expectedSelfUri,
+ final ShardingInspectionService service) {
+ return Arguments.of(name, new ShardingAlgorithmResourceHandler(resourceUriTemplate, resourceKind, service), new MCPUriVariables(uriVariables), rows, expectedSelfUri);
+ }
+
+ private static ShardingInspectionService createInspectionService(final List<Map<String, Object>> rows) {
+ ShardingInspectionService result = mock(ShardingInspectionService.class);
+ when(result.queryAlgorithmPlugins(any())).thenReturn(rows);
+ when(result.queryKeyGenerateAlgorithmPlugins(any())).thenReturn(rows);
+ when(result.queryAlgorithms(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryUnusedAlgorithms(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryTableRulesUsedAlgorithm(any(), eq("logic_db"), eq("inline_algorithm"))).thenReturn(rows);
+ return result;
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingGovernanceResourceHandlerTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingGovernanceResourceHandlerTest.java
new file mode 100644
index 00000000000..9e22f902eac
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingGovernanceResourceHandlerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ShardingGovernanceResourceHandlerTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertHandleArguments")
+ void assertHandle(final String name, final ShardingGovernanceResourceHandler handler, final MCPUriVariables uriVariables, final List<Map<String, Object>> rows,
+ final String expectedSelfUri) {
+ MCPDatabaseHandlerContext databaseContext = mock(MCPDatabaseHandlerContext.class);
+ when(databaseContext.getQueryFacade()).thenReturn(mock(MCPFeatureQueryFacade.class));
+ MCPResponse actual = handler.handle(databaseContext, uriVariables);
+ assertThat(((List<?>) actual.toPayload().get("items")).size(), is(rows.size()));
+ assertThat(actual.toPayload().get("self_uri"), is(expectedSelfUri));
+ }
+
+ private static Stream<Arguments> assertHandleArguments() {
+ List<Map<String, Object>> rows = List.of(Map.of("name", "dml_auditor"));
+ ShardingInspectionService service = createInspectionService(rows);
+ return Stream.of(
+ createArguments("auditors", "shardingsphere://features/sharding/databases/{database}/auditors",
+ ShardingGovernanceResourceHandler.ResourceKind.AUDITORS, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/auditors", service),
+ createArguments("unused auditors", "shardingsphere://features/sharding/databases/{database}/unused-auditors",
+ ShardingGovernanceResourceHandler.ResourceKind.UNUSED_AUDITORS, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/unused-auditors", service),
+ createArguments("auditor used table rules", "shardingsphere://features/sharding/databases/{database}/auditors/{auditor}/table-rules",
+ ShardingGovernanceResourceHandler.ResourceKind.AUDITOR_USED_TABLE_RULES, Map.of("database", "logic_db", "auditor", "dml_auditor"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/auditors/dml_auditor/table-rules", service),
+ createArguments("rule count", "shardingsphere://features/sharding/databases/{database}/rule-count",
+ ShardingGovernanceResourceHandler.ResourceKind.RULE_COUNT, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/rule-count", service));
+ }
+
+ private static Arguments createArguments(final String name, final String resourceUriTemplate, final ShardingGovernanceResourceHandler.ResourceKind resourceKind,
+ final Map<String, String> uriVariables, final List<Map<String, Object>> rows, final String expectedSelfUri,
+ final ShardingInspectionService service) {
+ return Arguments.of(name, new ShardingGovernanceResourceHandler(resourceUriTemplate, resourceKind, service), new MCPUriVariables(uriVariables), rows, expectedSelfUri);
+ }
+
+ private static ShardingInspectionService createInspectionService(final List<Map<String, Object>> rows) {
+ ShardingInspectionService result = mock(ShardingInspectionService.class);
+ when(result.queryAuditors(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryUnusedAuditors(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryTableRulesUsedAuditor(any(), eq("logic_db"), eq("dml_auditor"))).thenReturn(rows);
+ when(result.queryRuleCount(any(), eq("logic_db"))).thenReturn(rows);
+ return result;
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingStrategyResourceHandlerTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingStrategyResourceHandlerTest.java
new file mode 100644
index 00000000000..0ff2776f627
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingStrategyResourceHandlerTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ShardingStrategyResourceHandlerTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertHandleArguments")
+ void assertHandle(final String name, final ShardingStrategyResourceHandler handler, final MCPUriVariables uriVariables, final List<Map<String, Object>> rows,
+ final String expectedSelfUri) {
+ MCPDatabaseHandlerContext databaseContext = mock(MCPDatabaseHandlerContext.class);
+ when(databaseContext.getQueryFacade()).thenReturn(mock(MCPFeatureQueryFacade.class));
+ MCPResponse actual = handler.handle(databaseContext, uriVariables);
+ assertThat(((List<?>) actual.toPayload().get("items")).size(), is(rows.size()));
+ assertThat(actual.toPayload().get("self_uri"), is(expectedSelfUri));
+ }
+
+ private static Stream<Arguments> assertHandleArguments() {
+ List<Map<String, Object>> rows = List.of(Map.of("name", "snowflake_generator"));
+ ShardingInspectionService service = createInspectionService(rows);
+ return Stream.of(
+ createArguments("default strategy", "shardingsphere://features/sharding/databases/{database}/default-strategy",
+ ShardingStrategyResourceHandler.ResourceKind.DEFAULT_STRATEGY, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/default-strategy", service),
+ createArguments("key generators", "shardingsphere://features/sharding/databases/{database}/key-generators",
+ ShardingStrategyResourceHandler.ResourceKind.KEY_GENERATORS, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/key-generators", service),
+ createArguments("key generator", "shardingsphere://features/sharding/databases/{database}/key-generators/{keyGenerator}",
+ ShardingStrategyResourceHandler.ResourceKind.KEY_GENERATOR, Map.of("database", "logic_db", "keyGenerator", "snowflake_generator"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/key-generators/snowflake_generator", service),
+ createArguments("key generate strategies", "shardingsphere://features/sharding/databases/{database}/key-generate-strategies",
+ ShardingStrategyResourceHandler.ResourceKind.KEY_GENERATE_STRATEGIES, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/key-generate-strategies", service),
+ createArguments("key generate strategy", "shardingsphere://features/sharding/databases/{database}/key-generate-strategies/{strategy}",
+ ShardingStrategyResourceHandler.ResourceKind.KEY_GENERATE_STRATEGY, Map.of("database", "logic_db", "strategy", "order_key_strategy"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/key-generate-strategies/order_key_strategy", service),
+ createArguments("unused key generators", "shardingsphere://features/sharding/databases/{database}/unused-key-generators",
+ ShardingStrategyResourceHandler.ResourceKind.UNUSED_KEY_GENERATORS, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/unused-key-generators", service),
+ createArguments("key generator used table rules", "shardingsphere://features/sharding/databases/{database}/key-generators/{keyGenerator}/table-rules",
+ ShardingStrategyResourceHandler.ResourceKind.KEY_GENERATOR_USED_TABLE_RULES,
+ Map.of("database", "logic_db", "keyGenerator", "snowflake_generator"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/key-generators/snowflake_generator/table-rules", service));
+ }
+
+ private static Arguments createArguments(final String name, final String resourceUriTemplate, final ShardingStrategyResourceHandler.ResourceKind resourceKind,
+ final Map<String, String> uriVariables, final List<Map<String, Object>> rows, final String expectedSelfUri,
+ final ShardingInspectionService service) {
+ return Arguments.of(name, new ShardingStrategyResourceHandler(resourceUriTemplate, resourceKind, service), new MCPUriVariables(uriVariables), rows, expectedSelfUri);
+ }
+
+ private static ShardingInspectionService createInspectionService(final List<Map<String, Object>> rows) {
+ ShardingInspectionService result = mock(ShardingInspectionService.class);
+ when(result.queryDefaultStrategy(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryKeyGenerators(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryKeyGenerator(any(), eq("logic_db"), eq("snowflake_generator"))).thenReturn(rows);
+ when(result.queryKeyGenerateStrategies(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryKeyGenerateStrategy(any(), eq("logic_db"), eq("order_key_strategy"))).thenReturn(rows);
+ when(result.queryUnusedKeyGenerators(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryTableRulesUsedKeyGenerator(any(), eq("logic_db"), eq("snowflake_generator"))).thenReturn(rows);
+ return result;
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandlerTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandlerTest.java
new file mode 100644
index 00000000000..daae6b878eb
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/resource/handler/ShardingTableResourceHandlerTest.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.resource.handler;
+
+import org.apache.shardingsphere.mcp.api.protocol.response.MCPResponse;
+import org.apache.shardingsphere.mcp.api.resource.MCPUriVariables;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.service.ShardingInspectionService;
+import org.apache.shardingsphere.mcp.support.database.MCPDatabaseHandlerContext;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ShardingTableResourceHandlerTest {
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("assertHandleArguments")
+ void assertHandle(final String name, final ShardingTableResourceHandler handler, final MCPUriVariables uriVariables, final List<Map<String, Object>> rows,
+ final String expectedSelfUri) {
+ MCPDatabaseHandlerContext databaseContext = mock(MCPDatabaseHandlerContext.class);
+ when(databaseContext.getQueryFacade()).thenReturn(mock(MCPFeatureQueryFacade.class));
+ MCPResponse actual = handler.handle(databaseContext, uriVariables);
+ assertThat(((List<?>) actual.toPayload().get("items")).size(), is(rows.size()));
+ assertThat(actual.toPayload().get("self_uri"), is(expectedSelfUri));
+ }
+
+ private static Stream<Arguments> assertHandleArguments() {
+ List<Map<String, Object>> rows = List.of(Map.of("name", "t_order"));
+ ShardingInspectionService service = createInspectionService(rows);
+ return Stream.of(
+ createArguments("table rules", "shardingsphere://features/sharding/databases/{database}/table-rules",
+ ShardingTableResourceHandler.ResourceKind.TABLE_RULES, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/table-rules", service),
+ createArguments("table rule", "shardingsphere://features/sharding/databases/{database}/tables/{table}/table-rule",
+ ShardingTableResourceHandler.ResourceKind.TABLE_RULE, Map.of("database", "logic_db", "table", "t_order"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/tables/t_order/table-rule", service),
+ createArguments("table nodes", "shardingsphere://features/sharding/databases/{database}/table-nodes",
+ ShardingTableResourceHandler.ResourceKind.TABLE_NODES, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/table-nodes", service),
+ createArguments("table node", "shardingsphere://features/sharding/databases/{database}/tables/{table}/nodes",
+ ShardingTableResourceHandler.ResourceKind.TABLE_NODE, Map.of("database", "logic_db", "table", "t_order"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/tables/t_order/nodes", service),
+ createArguments("table reference rules", "shardingsphere://features/sharding/databases/{database}/table-reference-rules",
+ ShardingTableResourceHandler.ResourceKind.TABLE_REFERENCE_RULES, Map.of("database", "logic_db"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/table-reference-rules", service),
+ createArguments("table reference rule", "shardingsphere://features/sharding/databases/{database}/table-reference-rules/{rule}",
+ ShardingTableResourceHandler.ResourceKind.TABLE_REFERENCE_RULE, Map.of("database", "logic_db", "rule", "ref_rule"), rows,
+ "shardingsphere://features/sharding/databases/logic_db/table-reference-rules/ref_rule", service));
+ }
+
+ private static Arguments createArguments(final String name, final String resourceUriTemplate, final ShardingTableResourceHandler.ResourceKind resourceKind,
+ final Map<String, String> uriVariables, final List<Map<String, Object>> rows, final String expectedSelfUri,
+ final ShardingInspectionService service) {
+ return Arguments.of(name, new ShardingTableResourceHandler(resourceUriTemplate, resourceKind, service), new MCPUriVariables(uriVariables), rows, expectedSelfUri);
+ }
+
+ private static ShardingInspectionService createInspectionService(final List<Map<String, Object>> rows) {
+ ShardingInspectionService result = mock(ShardingInspectionService.class);
+ when(result.queryTableRules(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryTableRule(any(), eq("logic_db"), eq("t_order"))).thenReturn(rows);
+ when(result.queryTableNodes(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryTableNode(any(), eq("logic_db"), eq("t_order"))).thenReturn(rows);
+ when(result.queryTableReferenceRules(any(), eq("logic_db"))).thenReturn(rows);
+ when(result.queryTableReferenceRule(any(), eq("logic_db"), eq("ref_rule"))).thenReturn(rows);
+ return result;
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinderTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinderTest.java
new file mode 100644
index 00000000000..492c6146832
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/handler/ShardingPlanningRequestBinderTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.handler;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class ShardingPlanningRequestBinderTest {
+
+ @Test
+ void assertBindTableRule() {
+ ShardingWorkflowRequest actual = new ShardingPlanningRequestBinder().bindTableRule(Map.of(
+ "database", "logic_db",
+ "structured_intent_evidence", Map.of("table", "t_order", "column", "order_id", "algorithm_type", "INLINE"),
+ "user_overrides", Map.of("algorithm_properties", Map.of("algorithm-expression", "t_order_${order_id % 2}"), "auditors", "dml_auditor"))).toWorkflowRequest();
+ assertThat(actual.getDatabase(), is("logic_db"));
+ assertThat(actual.getTable(), is("t_order"));
+ assertThat(actual.getAlgorithmType(), is("INLINE"));
+ assertThat(actual.getPrimaryAlgorithmProperties(), is(Map.of("algorithm-expression", "t_order_${order_id % 2}")));
+ assertThat(actual.getAuditorNames(), is(List.of("dml_auditor")));
+ }
+
+ @Test
+ void assertBindTableReferenceRule() {
+ ShardingWorkflowRequest actual = new ShardingPlanningRequestBinder().bindTableReferenceRule(Map.of(
+ "database", "logic_db", "rule", "ref_rule", "reference_tables", "t_order,t_order_item")).toWorkflowRequest();
+ assertThat(actual.getDatabase(), is("logic_db"));
+ assertThat(actual.getRuleName(), is("ref_rule"));
+ assertThat(actual.getReferenceTables(), is(List.of("t_order", "t_order_item")));
+ }
+
+ @Test
+ void assertBindDefaultStrategy() {
+ ShardingWorkflowRequest actual = new ShardingPlanningRequestBinder().bindDefaultStrategy(Map.of(
+ "database", "logic_db", "default_strategy_type", "DATABASE", "structured_intent_evidence", Map.of("column", "order_id"),
+ "algorithm_properties", Map.of("algorithm-expression", "ds_${order_id % 2}"))).toWorkflowRequest();
+ assertThat(actual.getDefaultStrategyType(), is("DATABASE"));
+ assertThat(actual.getColumn(), is("order_id"));
+ assertThat(actual.getPrimaryAlgorithmProperties(), is(Map.of("algorithm-expression", "ds_${order_id % 2}")));
+ }
+
+ @Test
+ void assertBindKeyGenerator() {
+ ShardingWorkflowRequest actual = new ShardingPlanningRequestBinder().bindKeyGenerator(Map.of(
+ "database", "logic_db", "key_generator", "snowflake_generator", "user_overrides",
+ Map.of("key_generator_type", "SNOWFLAKE", "key_generator_properties", Map.of("worker-id", "1")))).toWorkflowRequest();
+ assertThat(actual.getKeyGeneratorName(), is("snowflake_generator"));
+ assertThat(actual.getKeyGeneratorType(), is("SNOWFLAKE"));
+ assertThat(actual.getKeyGeneratorProperties(), is(Map.of("worker-id", "1")));
+ }
+
+ @Test
+ void assertBindKeyGenerateStrategy() {
+ ShardingWorkflowRequest actual = new ShardingPlanningRequestBinder().bindKeyGenerateStrategy(Map.of(
+ "database", "logic_db", "key_generate_strategy", "order_key_strategy", "structured_intent_evidence",
+ Map.of("table", "t_order", "column", "id", "key_generator", "snowflake_generator"))).toWorkflowRequest();
+ assertThat(actual.getKeyGenerateStrategyName(), is("order_key_strategy"));
+ assertThat(actual.getTable(), is("t_order"));
+ assertThat(actual.getKeyGeneratorName(), is("snowflake_generator"));
+ }
+
+ @Test
+ void assertBindRuleComponentCleanup() {
+ ShardingWorkflowRequest actual = new ShardingPlanningRequestBinder().bindRuleComponentCleanup(Map.of(
+ "database", "logic_db", "component_type", "algorithm", "component_name", "unused_algorithm")).toWorkflowRequest();
+ assertThat(actual.getDatabase(), is("logic_db"));
+ assertThat(actual.getComponentType(), is("algorithm"));
+ assertThat(actual.getComponentName(), is("unused_algorithm"));
+ }
+}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingDefaultStrategyWorkflowRequestTest.java
similarity index 52%
copy from mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
copy to mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingDefaultStrategyWorkflowRequestTest.java
index 78d6c156bc8..e366d701b42 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingDefaultStrategyWorkflowRequestTest.java
@@ -15,32 +15,26 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.mcp.support.descriptor;
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
import org.junit.jupiter.api.Test;
-import java.util.Map;
-import java.util.Set;
-
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-class MCPPromptTemplateLoaderTest {
-
- private static final String TEMPLATE_RESOURCE = "META-INF/shardingsphere-mcp/prompts/fixture-markdown-resource.md";
-
- @Test
- void assertLoad() {
- assertThat(MCPPromptTemplateLoader.load(TEMPLATE_RESOURCE), is("Fixture template for {{database}}."));
- }
-
- @Test
- void assertExtractPlaceholders() {
- assertThat(MCPPromptTemplateLoader.extractPlaceholders("{{ database }} and {{schema_name}}"), is(Set.of("database", "schema_name")));
- }
+class ShardingDefaultStrategyWorkflowRequestTest {
@Test
- void assertRender() {
- assertThat(MCPPromptTemplateLoader.render("Hello {{ name }}{{ missing }}.", Map.of("name", "ShardingSphere")), is("Hello ShardingSphere."));
+ void assertToWorkflowRequest() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setDefaultStrategyType("DATABASE");
+ request.setStrategyType("standard");
+ request.setColumn("order_id");
+ ShardingDefaultStrategyWorkflowRequest defaultStrategyRequest = new ShardingDefaultStrategyWorkflowRequest(request);
+ request.setDefaultStrategyType("TABLE");
+ ShardingWorkflowRequest actual = defaultStrategyRequest.toWorkflowRequest();
+ actual.setDefaultStrategyType("TABLE");
+ assertThat(defaultStrategyRequest.toWorkflowRequest().getDefaultStrategyType(), is("DATABASE"));
+ assertThat(defaultStrategyRequest.toWorkflowRequest().getStrategyType(), is("standard"));
}
}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGenerateStrategyWorkflowRequestTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGenerateStrategyWorkflowRequestTest.java
new file mode 100644
index 00000000000..8379fb2f6de
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGenerateStrategyWorkflowRequestTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class ShardingKeyGenerateStrategyWorkflowRequestTest {
+
+ @Test
+ void assertToWorkflowRequest() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setKeyGenerateStrategyName("order_key_strategy");
+ request.setTable("t_order");
+ request.setColumn("order_id");
+ request.setKeyGeneratorName("snowflake_generator");
+ ShardingKeyGenerateStrategyWorkflowRequest strategyRequest = new ShardingKeyGenerateStrategyWorkflowRequest(request);
+ request.setKeyGenerateStrategyName("changed_strategy");
+ ShardingWorkflowRequest actual = strategyRequest.toWorkflowRequest();
+ actual.setKeyGenerateStrategyName("actual_changed_strategy");
+ assertThat(strategyRequest.toWorkflowRequest().getKeyGenerateStrategyName(), is("order_key_strategy"));
+ assertThat(strategyRequest.toWorkflowRequest().getKeyGeneratorName(), is("snowflake_generator"));
+ }
+}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGeneratorWorkflowRequestTest.java
similarity index 50%
copy from mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
copy to mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGeneratorWorkflowRequestTest.java
index 78d6c156bc8..d4d41ffdf7f 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingKeyGeneratorWorkflowRequestTest.java
@@ -15,32 +15,28 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.mcp.support.descriptor;
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
import org.junit.jupiter.api.Test;
import java.util.Map;
-import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-class MCPPromptTemplateLoaderTest {
-
- private static final String TEMPLATE_RESOURCE = "META-INF/shardingsphere-mcp/prompts/fixture-markdown-resource.md";
-
- @Test
- void assertLoad() {
- assertThat(MCPPromptTemplateLoader.load(TEMPLATE_RESOURCE), is("Fixture template for {{database}}."));
- }
-
- @Test
- void assertExtractPlaceholders() {
- assertThat(MCPPromptTemplateLoader.extractPlaceholders("{{ database }} and {{schema_name}}"), is(Set.of("database", "schema_name")));
- }
+class ShardingKeyGeneratorWorkflowRequestTest {
@Test
- void assertRender() {
- assertThat(MCPPromptTemplateLoader.render("Hello {{ name }}{{ missing }}.", Map.of("name", "ShardingSphere")), is("Hello ShardingSphere."));
+ void assertToWorkflowRequest() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setKeyGeneratorName("snowflake_generator");
+ request.setKeyGeneratorType("SNOWFLAKE");
+ request.putKeyGeneratorProperties(Map.of("worker-id", "1"));
+ ShardingKeyGeneratorWorkflowRequest keyGeneratorRequest = new ShardingKeyGeneratorWorkflowRequest(request);
+ request.putKeyGeneratorProperties(Map.of("worker-id", "2"));
+ ShardingWorkflowRequest actual = keyGeneratorRequest.toWorkflowRequest();
+ actual.putKeyGeneratorProperties(Map.of("worker-id", "3"));
+ assertThat(keyGeneratorRequest.toWorkflowRequest().getKeyGeneratorName(), is("snowflake_generator"));
+ assertThat(keyGeneratorRequest.toWorkflowRequest().getKeyGeneratorProperties(), is(Map.of("worker-id", "1")));
}
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingRuleComponentCleanupWorkflowRequestTest.java
similarity index 53%
copy from mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
copy to mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingRuleComponentCleanupWorkflowRequestTest.java
index 78d6c156bc8..b814ca8a328 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingRuleComponentCleanupWorkflowRequestTest.java
@@ -15,32 +15,25 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.mcp.support.descriptor;
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
import org.junit.jupiter.api.Test;
-import java.util.Map;
-import java.util.Set;
-
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-class MCPPromptTemplateLoaderTest {
-
- private static final String TEMPLATE_RESOURCE = "META-INF/shardingsphere-mcp/prompts/fixture-markdown-resource.md";
-
- @Test
- void assertLoad() {
- assertThat(MCPPromptTemplateLoader.load(TEMPLATE_RESOURCE), is("Fixture template for {{database}}."));
- }
-
- @Test
- void assertExtractPlaceholders() {
- assertThat(MCPPromptTemplateLoader.extractPlaceholders("{{ database }} and {{schema_name}}"), is(Set.of("database", "schema_name")));
- }
+class ShardingRuleComponentCleanupWorkflowRequestTest {
@Test
- void assertRender() {
- assertThat(MCPPromptTemplateLoader.render("Hello {{ name }}{{ missing }}.", Map.of("name", "ShardingSphere")), is("Hello ShardingSphere."));
+ void assertToWorkflowRequest() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setComponentType("algorithm");
+ request.setComponentName("unused_algorithm");
+ ShardingRuleComponentCleanupWorkflowRequest cleanupRequest = new ShardingRuleComponentCleanupWorkflowRequest(request);
+ request.setComponentName("changed_algorithm");
+ ShardingWorkflowRequest actual = cleanupRequest.toWorkflowRequest();
+ actual.setComponentName("actual_changed_algorithm");
+ assertThat(cleanupRequest.toWorkflowRequest().getComponentType(), is("algorithm"));
+ assertThat(cleanupRequest.toWorkflowRequest().getComponentName(), is("unused_algorithm"));
}
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableReferenceRuleWorkflowRequestTest.java
similarity index 51%
copy from mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
copy to mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableReferenceRuleWorkflowRequestTest.java
index 78d6c156bc8..54312c37741 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableReferenceRuleWorkflowRequestTest.java
@@ -15,32 +15,27 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.mcp.support.descriptor;
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
import org.junit.jupiter.api.Test;
-import java.util.Map;
-import java.util.Set;
+import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-class MCPPromptTemplateLoaderTest {
-
- private static final String TEMPLATE_RESOURCE = "META-INF/shardingsphere-mcp/prompts/fixture-markdown-resource.md";
-
- @Test
- void assertLoad() {
- assertThat(MCPPromptTemplateLoader.load(TEMPLATE_RESOURCE), is("Fixture template for {{database}}."));
- }
-
- @Test
- void assertExtractPlaceholders() {
- assertThat(MCPPromptTemplateLoader.extractPlaceholders("{{ database }} and {{schema_name}}"), is(Set.of("database", "schema_name")));
- }
+class ShardingTableReferenceRuleWorkflowRequestTest {
@Test
- void assertRender() {
- assertThat(MCPPromptTemplateLoader.render("Hello {{ name }}{{ missing }}.", Map.of("name", "ShardingSphere")), is("Hello ShardingSphere."));
+ void assertToWorkflowRequest() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setRuleName("ref_rule");
+ request.getReferenceTables().addAll(List.of("t_order", "t_order_item"));
+ ShardingTableReferenceRuleWorkflowRequest referenceRuleRequest = new ShardingTableReferenceRuleWorkflowRequest(request);
+ request.getReferenceTables().clear();
+ ShardingWorkflowRequest actual = referenceRuleRequest.toWorkflowRequest();
+ actual.getReferenceTables().clear();
+ assertThat(referenceRuleRequest.toWorkflowRequest().getRuleName(), is("ref_rule"));
+ assertThat(referenceRuleRequest.toWorkflowRequest().getReferenceTables(), is(List.of("t_order", "t_order_item")));
}
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableRuleWorkflowRequestTest.java
similarity index 50%
copy from mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
copy to mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableRuleWorkflowRequestTest.java
index 78d6c156bc8..b4b22333626 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/model/ShardingTableRuleWorkflowRequestTest.java
@@ -15,32 +15,29 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.mcp.support.descriptor;
+package org.apache.shardingsphere.mcp.feature.sharding.tool.model;
import org.junit.jupiter.api.Test;
import java.util.Map;
-import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-class MCPPromptTemplateLoaderTest {
-
- private static final String TEMPLATE_RESOURCE = "META-INF/shardingsphere-mcp/prompts/fixture-markdown-resource.md";
-
- @Test
- void assertLoad() {
- assertThat(MCPPromptTemplateLoader.load(TEMPLATE_RESOURCE), is("Fixture template for {{database}}."));
- }
-
- @Test
- void assertExtractPlaceholders() {
- assertThat(MCPPromptTemplateLoader.extractPlaceholders("{{ database }} and {{schema_name}}"), is(Set.of("database", "schema_name")));
- }
+class ShardingTableRuleWorkflowRequestTest {
@Test
- void assertRender() {
- assertThat(MCPPromptTemplateLoader.render("Hello {{ name }}{{ missing }}.", Map.of("name", "ShardingSphere")), is("Hello ShardingSphere."));
+ void assertToWorkflowRequest() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setTable("t_order");
+ request.setColumn("order_id");
+ request.setAlgorithmType("INLINE");
+ request.putAlgorithmProperties(Map.of("algorithm-expression", "t_order_${order_id % 2}"));
+ ShardingTableRuleWorkflowRequest tableRuleRequest = new ShardingTableRuleWorkflowRequest(request);
+ request.setTable("t_changed");
+ ShardingWorkflowRequest actual = tableRuleRequest.toWorkflowRequest();
+ actual.setTable("t_actual_changed");
+ assertThat(tableRuleRequest.toWorkflowRequest().getTable(), is("t_order"));
+ assertThat(tableRuleRequest.toWorkflowRequest().getPrimaryAlgorithmProperties(), is(Map.of("algorithm-expression", "t_order_${order_id % 2}")));
}
}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmPropertyTemplateServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmPropertyTemplateServiceTest.java
new file mode 100644
index 00000000000..33c89ad760a
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmPropertyTemplateServiceTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ShardingAlgorithmPropertyTemplateServiceTest {
+
+ private final ShardingAlgorithmPropertyTemplateService service = new ShardingAlgorithmPropertyTemplateService();
+
+ @Test
+ void assertFindAlgorithmRequirementsForInline() {
+ List<AlgorithmPropertyRequirement> actual = service.findAlgorithmRequirements("INLINE");
+ assertThat(actual.getFirst().getAlgorithmRole(), is("primary"));
+ assertThat(actual.getFirst().getPropertyKey(), is("algorithm-expression"));
+ assertTrue(actual.getFirst().isRequired());
+ }
+
+ @Test
+ void assertFindAlgorithmRequirementsForMod() {
+ assertThat(service.findAlgorithmRequirements("MOD").getFirst().getPropertyKey(), is("sharding-count"));
+ }
+
+ @Test
+ void assertFindKeyGeneratorRequirementsForSnowflake() {
+ List<AlgorithmPropertyRequirement> actual = service.findKeyGeneratorRequirements("SNOWFLAKE");
+ assertThat(actual.getFirst().getAlgorithmRole(), is("key_generator"));
+ assertThat(actual.getFirst().getPropertyKey(), is("worker-id"));
+ }
+
+ @Test
+ void assertFindKeyGeneratorRequirementsForUuid() {
+ assertTrue(service.findKeyGeneratorRequirements("UUID").isEmpty());
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationServiceTest.java
new file mode 100644
index 00000000000..34e15f3256a
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingAlgorithmRecommendationServiceTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmCandidate;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssue;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowIssueCode;
+import org.junit.jupiter.api.Test;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ShardingAlgorithmRecommendationServiceTest {
+
+ private final ShardingAlgorithmRecommendationService service = new ShardingAlgorithmRecommendationService();
+
+ @Test
+ void assertRecommendSpecifiedAlgorithms() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setAlgorithmType("inline");
+ request.setKeyGeneratorType("snowflake");
+ List<AlgorithmCandidate> actual = service.recommend(request, List.of(Map.of("type", "INLINE")), List.of(Map.of("type", "SNOWFLAKE")), true, true, new LinkedList<>());
+ assertThat(actual.get(0).getAlgorithmType(), is("INLINE"));
+ assertThat(actual.get(1).getAlgorithmRole(), is("key_generator"));
+ assertThat(actual.get(1).getAlgorithmType(), is("SNOWFLAKE"));
+ }
+
+ @Test
+ void assertRecommendDefaultAlgorithms() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ List<AlgorithmCandidate> actual = service.recommend(request, List.of(Map.of("type", "MOD"), Map.of("type", "INLINE")),
+ List.of(Map.of("type", "UUID"), Map.of("type", "SNOWFLAKE")), true, true, new LinkedList<>());
+ assertThat(actual.get(0).getAlgorithmType(), is("INLINE"));
+ assertThat(actual.get(1).getAlgorithmType(), is("SNOWFLAKE"));
+ }
+
+ @Test
+ void assertRecommendRejectsInvisibleAlgorithm() {
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setAlgorithmType("CLASS_BASED");
+ List<WorkflowIssue> issues = new LinkedList<>();
+ assertTrue(service.recommend(request, List.of(Map.of("type", "INLINE")), List.of(), true, false, issues).isEmpty());
+ assertThat(issues.getFirst().getCode(), is(WorkflowIssueCode.ALGORITHM_NOT_FOUND));
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDefaultStrategyWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDefaultStrategyWorkflowPlanningServiceTest.java
new file mode 100644
index 00000000000..d82cf52e45b
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingDefaultStrategyWorkflowPlanningServiceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingDefaultStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ShardingDefaultStrategyWorkflowPlanningServiceTest {
+
+ @Test
+ void assertPlan() {
+ ShardingWorkflowPlanningService delegate = mock(ShardingWorkflowPlanningService.class);
+ WorkflowSessionContext workflowSessionContext = mock(WorkflowSessionContext.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ WorkflowContextSnapshot expected = new WorkflowContextSnapshot();
+ when(delegate.planDefaultStrategy(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), any())).thenReturn(expected);
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setDefaultStrategyType("DATABASE");
+ WorkflowContextSnapshot actual = new ShardingDefaultStrategyWorkflowPlanningService(delegate)
+ .plan(workflowSessionContext, queryFacade, "session-1", new ShardingDefaultStrategyWorkflowRequest(request));
+ ArgumentCaptor<ShardingWorkflowRequest> requestCaptor = ArgumentCaptor.forClass(ShardingWorkflowRequest.class);
+ verify(delegate).planDefaultStrategy(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), requestCaptor.capture());
+ assertThat(actual, is(expected));
+ assertThat(requestCaptor.getValue().getDefaultStrategyType(), is("DATABASE"));
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGenerateStrategyWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGenerateStrategyWorkflowPlanningServiceTest.java
new file mode 100644
index 00000000000..f4f01527fa3
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGenerateStrategyWorkflowPlanningServiceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGenerateStrategyWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ShardingKeyGenerateStrategyWorkflowPlanningServiceTest {
+
+ @Test
+ void assertPlan() {
+ ShardingWorkflowPlanningService delegate = mock(ShardingWorkflowPlanningService.class);
+ WorkflowSessionContext workflowSessionContext = mock(WorkflowSessionContext.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ WorkflowContextSnapshot expected = new WorkflowContextSnapshot();
+ when(delegate.planKeyGenerateStrategy(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), any())).thenReturn(expected);
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setKeyGenerateStrategyName("order_key_strategy");
+ WorkflowContextSnapshot actual = new ShardingKeyGenerateStrategyWorkflowPlanningService(delegate)
+ .plan(workflowSessionContext, queryFacade, "session-1", new ShardingKeyGenerateStrategyWorkflowRequest(request));
+ ArgumentCaptor<ShardingWorkflowRequest> requestCaptor = ArgumentCaptor.forClass(ShardingWorkflowRequest.class);
+ verify(delegate).planKeyGenerateStrategy(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), requestCaptor.capture());
+ assertThat(actual, is(expected));
+ assertThat(requestCaptor.getValue().getKeyGenerateStrategyName(), is("order_key_strategy"));
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGeneratorWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGeneratorWorkflowPlanningServiceTest.java
new file mode 100644
index 00000000000..5cff343df45
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingKeyGeneratorWorkflowPlanningServiceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingKeyGeneratorWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ShardingKeyGeneratorWorkflowPlanningServiceTest {
+
+ @Test
+ void assertPlan() {
+ ShardingWorkflowPlanningService delegate = mock(ShardingWorkflowPlanningService.class);
+ WorkflowSessionContext workflowSessionContext = mock(WorkflowSessionContext.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ WorkflowContextSnapshot expected = new WorkflowContextSnapshot();
+ when(delegate.planKeyGenerator(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), any())).thenReturn(expected);
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setKeyGeneratorName("snowflake_generator");
+ WorkflowContextSnapshot actual = new ShardingKeyGeneratorWorkflowPlanningService(delegate)
+ .plan(workflowSessionContext, queryFacade, "session-1", new ShardingKeyGeneratorWorkflowRequest(request));
+ ArgumentCaptor<ShardingWorkflowRequest> requestCaptor = ArgumentCaptor.forClass(ShardingWorkflowRequest.class);
+ verify(delegate).planKeyGenerator(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), requestCaptor.capture());
+ assertThat(actual, is(expected));
+ assertThat(requestCaptor.getValue().getKeyGeneratorName(), is("snowflake_generator"));
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingRuleComponentCleanupWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingRuleComponentCleanupWorkflowPlanningServiceTest.java
new file mode 100644
index 00000000000..7c37691085e
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingRuleComponentCleanupWorkflowPlanningServiceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingRuleComponentCleanupWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ShardingRuleComponentCleanupWorkflowPlanningServiceTest {
+
+ @Test
+ void assertPlan() {
+ ShardingWorkflowPlanningService delegate = mock(ShardingWorkflowPlanningService.class);
+ WorkflowSessionContext workflowSessionContext = mock(WorkflowSessionContext.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ WorkflowContextSnapshot expected = new WorkflowContextSnapshot();
+ when(delegate.planComponentCleanup(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), any())).thenReturn(expected);
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setComponentName("unused_algorithm");
+ WorkflowContextSnapshot actual = new ShardingRuleComponentCleanupWorkflowPlanningService(delegate)
+ .plan(workflowSessionContext, queryFacade, "session-1", new ShardingRuleComponentCleanupWorkflowRequest(request));
+ ArgumentCaptor<ShardingWorkflowRequest> requestCaptor = ArgumentCaptor.forClass(ShardingWorkflowRequest.class);
+ verify(delegate).planComponentCleanup(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), requestCaptor.capture());
+ assertThat(actual, is(expected));
+ assertThat(requestCaptor.getValue().getComponentName(), is("unused_algorithm"));
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableReferenceRuleWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableReferenceRuleWorkflowPlanningServiceTest.java
new file mode 100644
index 00000000000..8c6760ac031
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableReferenceRuleWorkflowPlanningServiceTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableReferenceRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ShardingTableReferenceRuleWorkflowPlanningServiceTest {
+
+ @Test
+ void assertPlan() {
+ ShardingWorkflowPlanningService delegate = mock(ShardingWorkflowPlanningService.class);
+ WorkflowSessionContext workflowSessionContext = mock(WorkflowSessionContext.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ WorkflowContextSnapshot expected = new WorkflowContextSnapshot();
+ when(delegate.planTableReferenceRule(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), any())).thenReturn(expected);
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setRuleName("ref_rule");
+ request.getReferenceTables().addAll(List.of("t_order", "t_order_item"));
+ WorkflowContextSnapshot actual = new ShardingTableReferenceRuleWorkflowPlanningService(delegate)
+ .plan(workflowSessionContext, queryFacade, "session-1", new ShardingTableReferenceRuleWorkflowRequest(request));
+ ArgumentCaptor<ShardingWorkflowRequest> requestCaptor = ArgumentCaptor.forClass(ShardingWorkflowRequest.class);
+ verify(delegate).planTableReferenceRule(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), requestCaptor.capture());
+ assertThat(actual, is(expected));
+ assertThat(requestCaptor.getValue().getReferenceTables(), is(List.of("t_order", "t_order_item")));
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableRuleWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableRuleWorkflowPlanningServiceTest.java
new file mode 100644
index 00000000000..b69c8c587d1
--- /dev/null
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingTableRuleWorkflowPlanningServiceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.mcp.feature.sharding.tool.service;
+
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingTableRuleWorkflowRequest;
+import org.apache.shardingsphere.mcp.feature.sharding.tool.model.ShardingWorkflowRequest;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
+import org.apache.shardingsphere.mcp.support.workflow.WorkflowSessionContext;
+import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowContextSnapshot;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ShardingTableRuleWorkflowPlanningServiceTest {
+
+ @Test
+ void assertPlan() {
+ ShardingWorkflowPlanningService delegate = mock(ShardingWorkflowPlanningService.class);
+ WorkflowSessionContext workflowSessionContext = mock(WorkflowSessionContext.class);
+ MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+ WorkflowContextSnapshot expected = new WorkflowContextSnapshot();
+ when(delegate.planTableRule(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), any())).thenReturn(expected);
+ ShardingWorkflowRequest request = new ShardingWorkflowRequest();
+ request.setTable("t_order");
+ WorkflowContextSnapshot actual = new ShardingTableRuleWorkflowPlanningService(delegate)
+ .plan(workflowSessionContext, queryFacade, "session-1", new ShardingTableRuleWorkflowRequest(request));
+ ArgumentCaptor<ShardingWorkflowRequest> requestCaptor = ArgumentCaptor.forClass(ShardingWorkflowRequest.class);
+ verify(delegate).planTableRule(eq(workflowSessionContext), eq(queryFacade), eq("session-1"), requestCaptor.capture());
+ assertThat(actual, is(expected));
+ assertThat(requestCaptor.getValue().getTable(), is("t_order"));
+ }
+}
diff --git a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java
index 6c7153ab37c..e1214d9cb13 100644
--- a/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java
+++ b/mcp/features/sharding/src/test/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowPlanningServiceTest.java
@@ -46,9 +46,27 @@ class ShardingWorkflowPlanningServiceTest {
.planTableRule(new TestWorkflowSessionContext(), queryFacade, "session-1", createTableRuleRequest());
assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_PLANNED));
assertThat(actual.getWorkflowKind().getValue(), is("sharding.table.rule"));
+ assertThat(actual.getAlgorithmCandidates().getFirst().getAlgorithmType(), is("INLINE"));
+ assertThat(actual.getPropertyRequirements().getFirst().getPropertyKey(), is("algorithm-expression"));
assertTrue(actual.getRuleArtifacts().getFirst().getSql().startsWith("CREATE SHARDING TABLE RULE t_order"));
}
+ @Test
+ void assertPlanTableRuleClarifiesMissingAlgorithmProperties() {
+ ShardingInspectionService inspectionService = mock(ShardingInspectionService.class);
+ MCPFeatureQueryFacade queryFacade = createQueryFacade();
+ ShardingWorkflowRequest request = createTableRuleRequest();
+ request.setAlgorithmType("MOD");
+ request.getPrimaryAlgorithmProperties().clear();
+ when(inspectionService.queryTableRule(queryFacade, "logic_db", "t_order")).thenReturn(List.of());
+ WorkflowContextSnapshot actual = createPlanningService(inspectionService)
+ .planTableRule(new TestWorkflowSessionContext(), queryFacade, "session-1", request);
+ assertThat(actual.getStatus(), is(WorkflowLifecycle.STATUS_CLARIFYING));
+ assertThat(actual.getIssues().getFirst().getCode(), is(WorkflowIssueCode.REQUIRED_PROPERTY_MISSING));
+ assertThat(actual.getPropertyRequirements().getFirst().getPropertyKey(), is("sharding-count"));
+ assertTrue(actual.getRuleArtifacts().isEmpty());
+ }
+
@Test
void assertPlanTableRuleCreateWithComplexStrategy() {
ShardingWorkflowRequest request = createTableRuleRequest();
@@ -180,6 +198,8 @@ class ShardingWorkflowPlanningServiceTest {
when(inspectionService.queryKeyGenerator(queryFacade, "logic_db", "snowflake_generator")).thenReturn(List.of());
WorkflowContextSnapshot actual = createPlanningService(inspectionService)
.planKeyGenerator(new TestWorkflowSessionContext(), queryFacade, "session-1", createKeyGeneratorRequest());
+ assertThat(actual.getAlgorithmCandidates().getFirst().getAlgorithmRole(), is("key_generator"));
+ assertThat(actual.getPropertyRequirements().getFirst().getPropertyKey(), is("worker-id"));
assertTrue(actual.getRuleArtifacts().getFirst().getSql().startsWith("CREATE SHARDING KEY GENERATOR snowflake_generator"));
}
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
index d61ca2bdb05..2ea64e34db7 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidator.java
@@ -35,6 +35,8 @@ import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
final class MCPDescriptorCatalogValidator {
@@ -53,6 +55,10 @@ final class MCPDescriptorCatalogValidator {
private static final Collection<String> RECOVERY_CATEGORIES = List.of("not_found", "ambiguous", "empty_scope", "missing_context", "validation", "terminal",
"unsupported_target", "invalid_enum", "unsafe_sql", "stale_workflow", "unavailable_runtime", "terminal_operator_action");
+ private static final String CLIENT_FORM_ONLY_ARGUMENTS = "org.apache.shardingsphere/client-form-only-arguments";
+
+ private static final Pattern SINGLE_BRACE_PLACEHOLDER_PATTERN = Pattern.compile("(?<!\\{)\\{\\s*([a-zA-Z0-9_.-]+)\\s*}(?!})");
+
private MCPDescriptorCatalogValidator() {
}
@@ -293,11 +299,66 @@ final class MCPDescriptorCatalogValidator {
}
private static void validatePromptTemplate(final MCPPromptDescriptor descriptor, final MCPPromptTemplateBinding binding) {
+ String template = MCPPromptTemplateLoader.load(binding.getTemplateResource());
+ validateNoUnsupportedModelFacingPlaceholders(binding, template);
Set<String> declaredArguments = new HashSet<>(descriptor.getArguments().stream().map(MCPPromptArgumentDescriptor::getName).toList());
- for (String each : MCPPromptTemplateLoader.extractPlaceholders(MCPPromptTemplateLoader.load(binding.getTemplateResource()))) {
+ Set<String> renderedArguments = MCPPromptTemplateLoader.extractPlaceholders(template);
+ for (String each : renderedArguments) {
ShardingSpherePreconditions.checkState(declaredArguments.contains(each),
() -> new IllegalStateException(String.format("Prompt template `%s` has undeclared placeholder `%s`.", binding.getTemplateResource(), each)));
}
+ validateDeclaredPromptArgumentsRendered(descriptor, binding, declaredArguments, renderedArguments);
+ }
+
+ private static void validateNoUnsupportedModelFacingPlaceholders(final MCPPromptTemplateBinding binding, final String template) {
+ for (String each : template.lines().toList()) {
+ validateNoUnsupportedModelFacingPlaceholderInLine(binding, each);
+ }
+ }
+
+ private static void validateNoUnsupportedModelFacingPlaceholderInLine(final MCPPromptTemplateBinding binding, final String line) {
+ Matcher matcher = SINGLE_BRACE_PLACEHOLDER_PATTERN.matcher(line);
+ while (matcher.find()) {
+ if (!isResourceUriTemplateVariable(line, matcher.start())) {
+ throw new IllegalStateException(String.format("Prompt template `%s` contains unsupported model-facing placeholder `{%s}`.",
+ binding.getTemplateResource(), matcher.group(1)));
+ }
+ }
+ }
+
+ private static boolean isResourceUriTemplateVariable(final String line, final int placeholderStartIndex) {
+ int tokenStartIndex = findTokenStartIndex(line, placeholderStartIndex);
+ int resourceUriStartIndex = line.lastIndexOf("shardingsphere://", placeholderStartIndex);
+ return resourceUriStartIndex >= tokenStartIndex;
+ }
+
+ private static int findTokenStartIndex(final String line, final int index) {
+ for (int i = index - 1; i >= 0; i--) {
+ if (Character.isWhitespace(line.charAt(i))) {
+ return i + 1;
+ }
+ }
+ return 0;
+ }
+
+ private static void validateDeclaredPromptArgumentsRendered(final MCPPromptDescriptor descriptor, final MCPPromptTemplateBinding binding,
+ final Set<String> declaredArguments, final Set<String> renderedArguments) {
+ Set<String> clientFormOnlyArguments = getClientFormOnlyArguments(descriptor);
+ for (String each : declaredArguments) {
+ ShardingSpherePreconditions.checkState(renderedArguments.contains(each) || clientFormOnlyArguments.contains(each),
+ () -> new IllegalStateException(String.format("Prompt `%s` declares argument `%s` but template `%s` does not render it.",
+ descriptor.getName(), each, binding.getTemplateResource())));
+ }
+ }
+
+ private static Set<String> getClientFormOnlyArguments(final MCPPromptDescriptor descriptor) {
+ Object value = descriptor.getMeta().get(CLIENT_FORM_ONLY_ARGUMENTS);
+ if (null == value) {
+ return Set.of();
+ }
+ ShardingSpherePreconditions.checkState(value instanceof Collection,
+ () -> new IllegalStateException(String.format("Prompt `%s` metadata `%s` must be a list.", descriptor.getName(), CLIENT_FORM_ONLY_ARGUMENTS)));
+ return ((Collection<?>) value).stream().map(String::valueOf).collect(Collectors.toSet());
}
private static void validateCompletionTargetDescriptors(final Collection<MCPCompletionTargetDescriptor> descriptors, final Collection<MCPPromptDescriptor> prompts,
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
index 5e3e188e2e0..c14297d9c1e 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogValidatorTest.java
@@ -17,6 +17,8 @@
package org.apache.shardingsphere.mcp.support.descriptor;
+import org.apache.shardingsphere.mcp.api.prompt.descriptor.MCPPromptArgumentDescriptor;
+import org.apache.shardingsphere.mcp.api.prompt.descriptor.MCPPromptDescriptor;
import org.apache.shardingsphere.mcp.api.resource.descriptor.MCPResourceAnnotations;
import org.apache.shardingsphere.mcp.api.resource.descriptor.MCPResourceDescriptor;
import org.apache.shardingsphere.mcp.api.tool.descriptor.MCPToolAnnotations;
@@ -67,6 +69,40 @@ class MCPDescriptorCatalogValidatorTest {
"database_gateway_search_metadata", new MCPToolAnnotations("Search Metadata", true, false, true, true), createOutputSchema())))));
}
+ @Test
+ void assertValidateRejectsUnsupportedModelFacingPromptPlaceholder() {
+ assertValidationError(createPromptCatalog(List.of(createPromptDescriptor("test_prompt", List.of(), Map.of())),
+ List.of(new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md"))),
+ "Prompt template `META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md` contains unsupported model-facing placeholder `{database}`.");
+ }
+
+ @Test
+ void assertValidatePreservesResourceUriTemplatesInPrompt() {
+ assertDoesNotThrow(() -> MCPDescriptorCatalogValidator.validate(createPromptCatalog(List.of(createPromptDescriptor("test_prompt", List.of(), Map.of())),
+ List.of(new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md")))));
+ }
+
+ @Test
+ void assertValidateRejectsModelFacingPromptPlaceholderNearResourceUriTemplate() {
+ assertValidationError(createPromptCatalog(List.of(createPromptDescriptor("test_prompt", List.of(), Map.of())),
+ List.of(new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-mixed-placeholder-and-uri-template.md"))),
+ "Prompt template `META-INF/shardingsphere-mcp/prompts/fixture-mixed-placeholder-and-uri-template.md` contains unsupported model-facing placeholder `{database}`.");
+ }
+
+ @Test
+ void assertValidateRejectsUnrenderedPromptArgument() {
+ assertValidationError(createPromptCatalog(List.of(createPromptDescriptor("test_prompt", Map.of())),
+ List.of(new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md"))),
+ "Prompt `test_prompt` declares argument `database` but template `META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md` does not render it.");
+ }
+
+ @Test
+ void assertValidateAcceptsClientFormOnlyPromptArgument() {
+ assertDoesNotThrow(() -> MCPDescriptorCatalogValidator.validate(createPromptCatalog(List.of(createPromptDescriptor("test_prompt",
+ Map.of("org.apache.shardingsphere/client-form-only-arguments", List.of("database")))),
+ List.of(new MCPPromptTemplateBinding("test_prompt", "META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md")))));
+ }
+
private void assertValidationError(final MCPDescriptorCatalog catalog, final String expectedMessage) {
IllegalStateException actual = assertThrows(IllegalStateException.class, () -> MCPDescriptorCatalogValidator.validate(catalog));
assertThat(actual.getMessage(), is(expectedMessage));
@@ -81,11 +117,23 @@ class MCPDescriptorCatalogValidatorTest {
return new MCPDescriptorCatalog(resourceDescriptors, resourceTemplateDescriptors, List.of(), toolDescriptors, List.of(), List.of(), List.of(), List.of(), List.of());
}
+ private MCPDescriptorCatalog createPromptCatalog(final List<MCPPromptDescriptor> promptDescriptors, final List<MCPPromptTemplateBinding> promptTemplateBindings) {
+ return new MCPDescriptorCatalog(List.of(), List.of(), List.of(), List.of(), promptDescriptors, promptTemplateBindings, List.of(), List.of(), List.of());
+ }
+
private MCPResourceDescriptor createResourceDescriptor(final MCPResourceAnnotations annotations) {
return new MCPResourceDescriptor("shardingsphere://capabilities", "server-capability-catalog", "Server Capability Catalog",
"Read the model-facing capability catalog.", "application/json", annotations, Map.of());
}
+ private MCPPromptDescriptor createPromptDescriptor(final String name, final Map<String, Object> meta) {
+ return createPromptDescriptor(name, List.of(new MCPPromptArgumentDescriptor("database", "Database", "Logical database.", false)), meta);
+ }
+
+ private MCPPromptDescriptor createPromptDescriptor(final String name, final List<MCPPromptArgumentDescriptor> args, final Map<String, Object> meta) {
+ return new MCPPromptDescriptor(name, "Test Prompt", "Guide the model through a test prompt.", args, meta);
+ }
+
private MCPToolDescriptor createToolDescriptor(final MCPToolAnnotations annotations) {
return createToolDescriptor("database_gateway_test_tool", annotations, createOutputSchema());
}
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
index 78d6c156bc8..39dbe225db5 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPPromptTemplateLoaderTest.java
@@ -43,4 +43,10 @@ class MCPPromptTemplateLoaderTest {
void assertRender() {
assertThat(MCPPromptTemplateLoader.render("Hello {{ name }}{{ missing }}.", Map.of("name", "ShardingSphere")), is("Hello ShardingSphere."));
}
+
+ @Test
+ void assertRenderSupportedPlaceholders() {
+ assertThat(MCPPromptTemplateLoader.render("{{ database }} {{schema-name}} {{ arg.value }}",
+ Map.of("database", "logic_db", "schema-name", "public", "arg.value", "enabled")), is("logic_db public enabled"));
+ }
}
diff --git a/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-mixed-placeholder-and-uri-template.md b/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-mixed-placeholder-and-uri-template.md
new file mode 100644
index 00000000000..80a9abb902c
--- /dev/null
+++ b/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-mixed-placeholder-and-uri-template.md
@@ -0,0 +1 @@
+Read {database} before shardingsphere://databases/{database}/rules.
diff --git a/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md b/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md
new file mode 100644
index 00000000000..61cd37a0161
--- /dev/null
+++ b/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-single-brace-placeholder.md
@@ -0,0 +1 @@
+Fixture prompt for {database}.
diff --git a/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md b/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md
new file mode 100644
index 00000000000..25606c8a5e3
--- /dev/null
+++ b/mcp/support/src/test/resources/META-INF/shardingsphere-mcp/prompts/fixture-uri-template.md
@@ -0,0 +1 @@
+Read shardingsphere://features/test/databases/{database}/rules before planning.
diff --git a/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml b/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml
index b68c75bd54b..37991d4f9ad 100644
--- a/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml
+++ b/test/e2e/mcp/src/test/resources/baseline-contract/model-contract/capabilities.yaml
@@ -171,9 +171,9 @@ common_flows:
referenced_resources: ['shardingsphere://capabilities']
protocolAvailability: {resources: true, resourceTemplates: true, tools: true, toolAnnotations: true,
toolOutputSchemas: true, prompts: true, completions: true, resourceNavigation: true}
-fingerprints: {algorithm: sha256, descriptorCatalog: 38af8134f114058ef606db39d3c2901e05efa50b45f1d90212f9c3298da12477,
- promptSet: 61bd579b26477b5ac1e65c0325dae45aaa856ed240a9f0baaf27329fc0386c7f, resourceNavigation: d14e4f394dfb82f37109463f47222259bf9ae5631a4fa1f152e424ea123639e1,
- modelFacingSchemas: 0f0d55d3301fb3b07d3fa7f63e32c3dd359b53b3b7a5cfba65f199d89c3a175a}
+fingerprints: {algorithm: sha256, descriptorCatalog: 1aca15b3768ed51480afcead29551f416b9e2b436c2767f02882e9ee47ea6e56,
+ promptSet: d62c9b6141fc822c6b3dcfef2c9f18142c26cda1c461b4a29e6c788e4bcf2180, resourceNavigation: 1cede5d034541e4b0b3c0355360f6c41be7719bc0619e5bb2bd5cd342d5602c7,
+ modelFacingSchemas: a84507c66039d130043200e4728711803309a249d2da18e1698095f4c6c67a3e}
resources:
- uri: shardingsphere://capabilities
name: server-capability-catalog
@@ -754,6 +754,7 @@ prompts:
- {name: database, required: false}
- {name: operation_type, required: false}
- {name: algorithm_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_encrypt_rule
title: Plan Encrypt Rule
arguments:
@@ -784,6 +785,7 @@ prompts:
- {name: read_storage_units, required: false}
- {name: transactional_read_query_strategy, required: false}
- {name: load_balancer_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_readwrite_splitting_status
title: Plan Readwrite-Splitting Status
arguments:
@@ -791,11 +793,13 @@ prompts:
- {name: rule, required: false}
- {name: storage_unit, required: false}
- {name: target_status, required: false}
+ - {name: plan_id, required: false}
- name: plan_shadow_algorithm_cleanup
title: Plan Shadow Algorithm Cleanup
arguments:
- {name: database, required: false}
- {name: algorithm_name, required: false}
+ - {name: plan_id, required: false}
- name: plan_shadow_rule
title: Plan Shadow Rule
arguments:
@@ -806,40 +810,47 @@ prompts:
- {name: shadow_storage_unit, required: false}
- {name: table, required: false}
- {name: algorithm_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_sharding_default_strategy
title: Plan Sharding Default Strategy
arguments:
- {name: database, required: false}
- {name: operation_type, required: false}
- {name: algorithm_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_sharding_key_generate_strategy
title: Plan Sharding Key Generate Strategy
arguments:
- {name: database, required: false}
- {name: operation_type, required: false}
- {name: key_generator_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_sharding_key_generator
title: Plan Sharding Key Generator
arguments:
- {name: database, required: false}
- {name: operation_type, required: false}
- {name: key_generator_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_sharding_rule_component_cleanup
title: Plan Sharding Rule Component Cleanup
arguments:
- {name: database, required: false}
- {name: operation_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_sharding_table_reference_rule
title: Plan Sharding Table Reference Rule
arguments:
- {name: database, required: false}
- {name: operation_type, required: false}
+ - {name: plan_id, required: false}
- name: plan_sharding_table_rule
title: Plan Sharding Table Rule
arguments:
- {name: database, required: false}
- {name: operation_type, required: false}
- {name: algorithm_type, required: false}
+ - {name: plan_id, required: false}
- name: recover_workflow
title: Recover Workflow
arguments: