You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/07/15 12:43:59 UTC

(shardingsphere) branch master updated: Refactor MCP SQL analysis with database-aware parsers (#39130)

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 e249deba59d Refactor MCP SQL analysis with database-aware parsers (#39130)
e249deba59d is described below

commit e249deba59d3583dc5464292672c5c73a92c2832
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Wed Jul 15 20:43:52 2026 +0800

    Refactor MCP SQL analysis with database-aware parsers (#39130)
    
    * Refactor MCP SQL analysis with database-aware parsers
    
    * Refactor MCP SQL analysis with database-aware parsers
    
    * Refactor MCP SQL analysis with database-aware parsers
---
 .../merge/dql/ShardingDQLResultMergerTest.java     |   2 +-
 .../expr/groovy/GroovyInlineExpressionParser.java  |  16 -
 .../instructions/server-instructions.md            |   4 +-
 .../prompt/MCPPromptSpecificationFactoryTest.java  |   4 +-
 mcp/core/pom.xml                                   |  78 +++++
 .../MCPUnsupportedSQLStatementException.java       |   4 +
 .../tool/handler/execute/ClassificationResult.java |  32 +-
 .../handler/execute/ExecuteQueryToolHandler.java   |   8 +-
 .../handler/execute/ExecuteUpdateToolHandler.java  |   8 +-
 .../execute/ExplainSQLCandidateValidator.java      |  12 +-
 .../handler/execute/MCPSQLExecutionFacade.java     |  46 +--
 .../tool/handler/execute/MCPStatementAnalyzer.java | 217 ++++++++++++
 .../execute/SQLExecutionToolHandlerSupport.java    |  10 +
 .../handler/execute/SQLStatementClassResolver.java |  41 ---
 .../execute/SQLStatementObjectExtractor.java       | 327 ++++++++++++++++++
 .../handler/execute/SQLStatementObjectName.java    |  46 +--
 .../execute/SQLStatementObjectNameReader.java      |  93 -----
 .../execute/SQLStatementSafetyValidator.java       |  96 +++---
 .../tool/handler/execute/SQLStatementScanner.java  |  88 -----
 .../handler/execute/SQLStatementStructure.java     |  51 ---
 .../execute/SQLStatementStructureResolver.java     | 108 ------
 .../execute/SQLStatementTargetResolver.java        | 380 ---------------------
 .../tool/handler/execute/StatementClassifier.java  | 132 -------
 .../mcp-descriptors/mcp-descriptor-core.yaml       |  40 +--
 .../performance/MCPPerformanceBudgetSmokeTest.java |  17 -
 .../core/protocol/error/MCPErrorConverterTest.java |   4 +-
 .../capability/ServerCapabilitiesHandlerTest.java  |  29 +-
 .../execute/ExecuteQueryToolHandlerTest.java       |  21 +-
 .../execute/ExecuteUpdateToolHandlerTest.java      |  21 +-
 .../execute/ExplainSQLCandidateValidatorTest.java  |  23 +-
 .../execute/MCPJdbcStatementExecutorTest.java      |  57 ++--
 .../MCPJdbcTransactionStatementExecutorTest.java   |  33 +-
 .../handler/execute/MCPSQLExecutionFacadeTest.java | 121 ++++---
 .../handler/execute/MCPStatementAnalyzerTest.java  | 310 +++++++++++++++++
 .../execute/RuleDistSQLExecutionExceptionTest.java |   2 +-
 .../handler/execute/StatementClassifierTest.java   | 342 -------------------
 mcp/features/broadcast/pom.xml                     |   6 +
 mcp/features/encrypt/pom.xml                       |   6 +
 mcp/features/mask/pom.xml                          |   6 +
 mcp/features/readwrite-splitting/pom.xml           |  11 +
 ...teSplittingRuleDistSQLStatementClassifier.java} |  17 +-
 ....database.spi.MCPRuleDistSQLStatementClassifier |  18 +
 ...plittingRuleDistSQLStatementClassifierTest.java |  53 +++
 mcp/features/shadow/pom.xml                        |   6 +
 mcp/features/sharding/pom.xml                      |   6 +
 mcp/support/pom.xml                                |   5 +
 .../spi/MCPRuleDistSQLStatementClassifier.java}    |  30 +-
 .../descriptor/MCPGuidancePayloadBuilder.java      |   2 +-
 .../mcp-descriptors/mcp-descriptor-support.yaml    |   4 +-
 .../prompts/safe-sql-execution.md                  |   6 +-
 .../repository/standalone/jdbc/JDBCRepository.java |   7 -
 .../ModifyPartitionDefinitionSegment.java          |  11 -
 .../LLMMCPConversationRunnerFailureTest.java       |  16 -
 .../llm/conversation/LLMMCPSafetyValidator.java    |  24 --
 .../conversation/LLMMCPSafetyValidatorTest.java    |  23 --
 55 files changed, 1417 insertions(+), 1663 deletions(-)

diff --git a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/ShardingDQLResultMergerTest.java b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/ShardingDQLResultMergerTest.java
index f9cf5cc8d0e..848fa8c3f66 100644
--- a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/ShardingDQLResultMergerTest.java
+++ b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/ShardingDQLResultMergerTest.java
@@ -166,7 +166,7 @@ class ShardingDQLResultMergerTest {
     
     @Test
     void assertBuildIteratorStreamMergedResultWithSQLServerOffsetFetch() throws SQLException {
-        final ShardingDQLResultMerger resultMerger = new ShardingDQLResultMerger(sqlserverDatabaseType);
+        ShardingDQLResultMerger resultMerger = new ShardingDQLResultMerger(sqlserverDatabaseType);
         ShardingSphereDatabase database = mock(ShardingSphereDatabase.class, RETURNS_DEEP_STUBS);
         SelectStatement selectStatement = buildSelectStatement(sqlserverDatabaseType);
         selectStatement = withProjections(selectStatement, new ProjectionsSegment(0, 0));
diff --git a/infra/expr/type/groovy/src/main/java/org/apache/shardingsphere/infra/expr/groovy/GroovyInlineExpressionParser.java b/infra/expr/type/groovy/src/main/java/org/apache/shardingsphere/infra/expr/groovy/GroovyInlineExpressionParser.java
index 2142f24dbd5..7c9ddf14cc3 100644
--- a/infra/expr/type/groovy/src/main/java/org/apache/shardingsphere/infra/expr/groovy/GroovyInlineExpressionParser.java
+++ b/infra/expr/type/groovy/src/main/java/org/apache/shardingsphere/infra/expr/groovy/GroovyInlineExpressionParser.java
@@ -82,21 +82,10 @@ public final class GroovyInlineExpressionParser implements InlineExpressionParse
         return handlePlaceHolder(inlineExpression);
     }
     
-    /**
-     * Replace all inline expression placeholders.
-     *
-     * @param inlineExpression inline expression with {@code $->}
-     * @return result inline expression with {@code $}
-     */
     private String handlePlaceHolder(final String inlineExpression) {
         return inlineExpression.contains("$->{") ? inlineExpression.replaceAll("\\$->\\{", "\\${") : inlineExpression;
     }
     
-    /**
-     * Split and Evaluate inline expression. This function will replace all inline expression placeholders.
-     *
-     * @return result inline expression with {@code $}
-     */
     @Override
     public List<String> splitAndEvaluate() {
         if (Strings.isNullOrEmpty(inlineExpression)) {
@@ -108,11 +97,6 @@ public final class GroovyInlineExpressionParser implements InlineExpressionParse
         return flatten(evaluate(GroovyUtils.split(handlePlaceHolder(inlineExpression))));
     }
     
-    /**
-     * Turn inline expression into Groovy Closure. This function will replace all inline expression placeholders.
-     * For compatibility reasons, it does not check whether the unit of the input parameter map is null.
-     * @return The result of the Groovy Closure pattern.
-     */
     @Override
     public String evaluateWithArgs(final Map<String, Comparable<?>> map) {
         if (isConstantExpression(inlineExpression)) {
diff --git a/mcp/bootstrap/src/main/resources/META-INF/shardingsphere-mcp/instructions/server-instructions.md b/mcp/bootstrap/src/main/resources/META-INF/shardingsphere-mcp/instructions/server-instructions.md
index 245e574a2d4..bf28213d005 100644
--- a/mcp/bootstrap/src/main/resources/META-INF/shardingsphere-mcp/instructions/server-instructions.md
+++ b/mcp/bootstrap/src/main/resources/META-INF/shardingsphere-mcp/instructions/server-instructions.md
@@ -21,9 +21,9 @@ Use MCP list methods (`tools/list`, `resources/list`, `resources/templates/list`
 
 Use `completion/complete` for supported argument values. Read `shardingsphere://capabilities` when ShardingSphere domain capability guidance, workflow guidance, or side-effect notes are needed.
 
-Use `database_gateway_execute_query` only for classifier-approved `SELECT`.
+Use `database_gateway_execute_query` only for parser-approved `SELECT`.
 Use `database_gateway_execute_explain_query` for execution-plan diagnostics.
 Use `database_gateway_execute_update` with `execution_mode=preview` before side effects.
-Treat `database_gateway_execute_update` preview as classification-only, not as a database dry run.
+Treat `database_gateway_execute_update` preview as database-aware validation and classification, not as a database dry run.
 
 Continue from `next_actions` or `recovery.next_actions` instead of guessing hidden tools or arguments.
diff --git a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/prompt/MCPPromptSpecificationFactoryTest.java b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/prompt/MCPPromptSpecificationFactoryTest.java
index cb9b415ada2..a3cd15d7544 100644
--- a/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/prompt/MCPPromptSpecificationFactoryTest.java
+++ b/mcp/bootstrap/src/test/java/org/apache/shardingsphere/mcp/bootstrap/transport/capability/prompt/MCPPromptSpecificationFactoryTest.java
@@ -70,8 +70,8 @@ class MCPPromptSpecificationFactoryTest {
         assertRenderedLines(actualText, List.of(
                 "- database: logic_db",
                 "- sql_intent: count orders",
-                "2. Use database_gateway_execute_query only for one classifier-approved SELECT statement.",
-                "   Treat preview as classification-only, not as a database dry run.",
+                "2. Use database_gateway_execute_query only for one parser-approved SELECT statement.",
+                "   Treat preview as database-aware validation and classification, not as a database dry run.",
                 "6. Never split or batch multiple SQL statements into one MCP call."));
     }
     
diff --git a/mcp/core/pom.xml b/mcp/core/pom.xml
index b7388a5cfb1..7439c2d78db 100644
--- a/mcp/core/pom.xml
+++ b/mcp/core/pom.xml
@@ -37,5 +37,83 @@
             <artifactId>shardingsphere-infra-exception</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-infra-parser</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-distsql-statement</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-mysql</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-postgresql</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-opengauss</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-oracle</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-sqlserver</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-firebird</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-clickhouse</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-hive</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-engine-presto</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-database-connector-mariadb</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-sharding-distsql-parser</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/exception/MCPUnsupportedSQLStatementException.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/exception/MCPUnsupportedSQLStatementException.java
index 924ed09f5cf..de3af66cfe5 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/exception/MCPUnsupportedSQLStatementException.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/exception/MCPUnsupportedSQLStatementException.java
@@ -29,4 +29,8 @@ public final class MCPUnsupportedSQLStatementException extends MCPUnsupportedExc
     public MCPUnsupportedSQLStatementException() {
         super("Statement is not supported by the MCP contract.");
     }
+    
+    public MCPUnsupportedSQLStatementException(final RuntimeException cause) {
+        super("Statement is not supported by the MCP contract.", cause);
+    }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ClassificationResult.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ClassificationResult.java
index 2fcb3f9af96..6cc5056dd0d 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ClassificationResult.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ClassificationResult.java
@@ -23,8 +23,6 @@ import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPSta
 
 import java.util.Collection;
 import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
 import java.util.Optional;
 
 /**
@@ -33,16 +31,6 @@ import java.util.Optional;
 @Getter
 public final class ClassificationResult {
     
-    private static final Collection<String> RULE_DIST_SQL_PREFIXES = List.of(
-            "CREATE SHARDING ", "ALTER SHARDING ", "DROP SHARDING ",
-            "CREATE DEFAULT SHARDING ", "ALTER DEFAULT SHARDING ", "DROP DEFAULT SHARDING ",
-            "CREATE BROADCAST ", "DROP BROADCAST ",
-            "CREATE ENCRYPT ", "ALTER ENCRYPT ", "DROP ENCRYPT ",
-            "CREATE MASK ", "ALTER MASK ", "DROP MASK ",
-            "CREATE SHADOW ", "ALTER SHADOW ", "DROP SHADOW ",
-            "CREATE DEFAULT SHADOW ", "ALTER DEFAULT SHADOW ", "DROP DEFAULT SHADOW ",
-            "CREATE READWRITE_SPLITTING ", "ALTER READWRITE_SPLITTING ", "DROP READWRITE_SPLITTING ");
-    
     private final SupportedMCPStatement statementClass;
     
     private final String statementType;
@@ -58,21 +46,24 @@ public final class ClassificationResult {
     
     private final String savepointName;
     
+    private final boolean ruleDistSQL;
+    
     ClassificationResult(final SupportedMCPStatement statementClass, final String statementType, final String normalizedSql, final String savepointName,
-                         final Collection<SQLStatementObjectName> referencedObjects) {
+                         final Collection<SQLStatementObjectName> referencedObjects, final boolean ruleDistSQL) {
         this.statementClass = statementClass;
         this.statementType = statementType;
         this.normalizedSql = normalizedSql;
-        targetObjectName = referencedObjects.isEmpty() ? "" : referencedObjects.iterator().next().objectName();
+        targetObjectName = referencedObjects.isEmpty() ? "" : referencedObjects.iterator().next().getObjectName();
         referencedObjectNames = createReferencedObjectNames(referencedObjects);
         this.referencedObjects = referencedObjects;
         this.savepointName = savepointName;
+        this.ruleDistSQL = ruleDistSQL;
     }
     
     private Collection<String> createReferencedObjectNames(final Collection<SQLStatementObjectName> referencedObjects) {
         Collection<String> result = new LinkedHashSet<>(referencedObjects.size(), 1F);
         for (SQLStatementObjectName each : referencedObjects) {
-            result.add(each.objectName());
+            result.add(each.getObjectName());
         }
         return result;
     }
@@ -105,16 +96,7 @@ public final class ClassificationResult {
      * @return true when the statement is a recognized rule DistSQL statement
      */
     public boolean isRuleDistSQL() {
-        if (SupportedMCPStatement.DDL != statementClass) {
-            return false;
-        }
-        String upperSql = normalizedSql.toUpperCase(Locale.ENGLISH);
-        for (String each : RULE_DIST_SQL_PREFIXES) {
-            if (upperSql.startsWith(each)) {
-                return true;
-            }
-        }
-        return false;
+        return ruleDistSQL;
     }
     
     /**
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandler.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandler.java
index 6ebef5422f2..7d7d9e04591 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandler.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandler.java
@@ -48,17 +48,17 @@ public final class ExecuteQueryToolHandler implements MCPToolHandler<MCPDatabase
     public MCPSuccessPayload handle(final MCPDatabaseRequestContext databaseContext, final Map<String, Object> arguments) {
         MCPToolArguments toolArguments = new MCPToolArguments(arguments);
         String sql = toolArguments.getStringArgument("sql");
-        checkReadOnlyQuery(toolArguments, sql);
+        checkReadOnlyQuery(databaseContext, toolArguments, sql);
         SQLExecutionToolHandlerSupport.checkExecutionArguments(toolArguments, TOOL_NAME);
         return SQLExecutionPayload.query(databaseContext.getExecutionFacade().execute(SQLExecutionToolHandlerSupport.createReadOnlyExecutionRequest(databaseContext.getSessionId(), toolArguments,
                 SQLExecutionToolHandlerSupport.resolveSchema(databaseContext, toolArguments), sql, TOOL_NAME)));
     }
     
-    private void checkReadOnlyQuery(final MCPToolArguments toolArguments, final String sql) {
-        ClassificationResult classificationResult = new StatementClassifier().classify(sql);
+    private void checkReadOnlyQuery(final MCPDatabaseRequestContext databaseContext, final MCPToolArguments toolArguments, final String sql) {
+        ClassificationResult classificationResult = SQLExecutionToolHandlerSupport.analyze(databaseContext, toolArguments, sql);
         if (!SQLExecutionToolHandlerSupport.isQueryStatement(classificationResult)) {
             throw new SQLToolMismatchException(
-                    "database_gateway_execute_query only supports classifier-approved QUERY statements. "
+                    "database_gateway_execute_query only supports parser-approved QUERY statements. "
                             + "Use database_gateway_execute_explain_query for EXPLAIN diagnostics or database_gateway_execute_update for side-effecting SQL.",
                     TOOL_NAME, "database_gateway_execute_update", classificationResult,
                     createSuggestedArguments(toolArguments, classificationResult));
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandler.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandler.java
index 6ba93276c50..3ef39a47faa 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandler.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandler.java
@@ -52,7 +52,7 @@ public final class ExecuteUpdateToolHandler implements MCPToolHandler<MCPDatabas
     private static final String RESULT_KIND_PREVIEW = "preview";
     
     private static final String PREVIEW_REVIEW_GUIDANCE = "Review normalized_sql and side_effect_scope before execution. "
-            + "This preview is classification-only; it does not guarantee parsing, rule validation, algorithm initialization, affected rows, or runtime success.";
+            + "This preview performs database-aware validation and classification; it does not guarantee rule validation, algorithm initialization, affected rows, or runtime success.";
     
     private static final String RULE_DIST_SQL_PREVIEW_REVIEW_GUIDANCE = PREVIEW_REVIEW_GUIDANCE
             + " For natural-language rule changes, prefer the matching database_gateway_plan_* workflow tool before raw execution.";
@@ -77,7 +77,7 @@ public final class ExecuteUpdateToolHandler implements MCPToolHandler<MCPDatabas
         String executionMode = resolveExecutionMode(toolArguments);
         SQLExecutionToolHandlerSupport.checkExecutionArguments(toolArguments, TOOL_NAME);
         String sql = toolArguments.getStringArgument("sql");
-        ClassificationResult classificationResult = checkUpdateStatement(toolArguments, sql);
+        ClassificationResult classificationResult = checkUpdateStatement(databaseContext, toolArguments, sql);
         if (EXECUTION_MODE_PREVIEW.equals(executionMode)) {
             return createPreviewResponse(toolArguments, classificationResult);
         }
@@ -85,8 +85,8 @@ public final class ExecuteUpdateToolHandler implements MCPToolHandler<MCPDatabas
                 SQLExecutionToolHandlerSupport.createExecutionRequest(databaseContext.getSessionId(), toolArguments, sql, TOOL_NAME)));
     }
     
-    private ClassificationResult checkUpdateStatement(final MCPToolArguments toolArguments, final String sql) {
-        ClassificationResult classificationResult = new StatementClassifier().classify(sql);
+    private ClassificationResult checkUpdateStatement(final MCPDatabaseRequestContext databaseContext, final MCPToolArguments toolArguments, final String sql) {
+        ClassificationResult classificationResult = SQLExecutionToolHandlerSupport.analyze(databaseContext, toolArguments, sql);
         if (SQLExecutionToolHandlerSupport.isQueryStatement(classificationResult)) {
             throw new SQLToolMismatchException("database_gateway_execute_update does not accept read-only SQL. Use database_gateway_execute_query for read-only SQL.",
                     TOOL_NAME, "database_gateway_execute_query", classificationResult,
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidator.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidator.java
index faf6024e166..ab2c0f55316 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidator.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidator.java
@@ -17,13 +17,17 @@
 
 package org.apache.shardingsphere.mcp.core.tool.handler.execute;
 
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
 
 import java.util.List;
 import java.util.Set;
 
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
 final class ExplainSQLCandidateValidator {
     
     private static final Set<String> STATEMENT_START_KEYWORDS = Set.of(
@@ -33,12 +37,12 @@ final class ExplainSQLCandidateValidator {
             "DO", "COPY", "LOAD", "UNLOAD", "IMPORT", "EXPORT", "BEGIN", "START", "COMMIT", "ROLLBACK", "SAVEPOINT", "RELEASE",
             "SET", "RESET", "USE", "PARALLEL");
     
-    private final StatementClassifier statementClassifier = new StatementClassifier();
+    private final MCPStatementAnalyzer statementAnalyzer;
     
     private final SQLStatementScanner scanner = new SQLStatementScanner();
     
-    ClassificationResult validate(final String sql, final String explainSql) {
-        ClassificationResult explainedStatement = statementClassifier.classify(sql);
+    ClassificationResult validate(final String sql, final String explainSql, final MCPDatabaseCapability databaseCapability) {
+        ClassificationResult explainedStatement = statementAnalyzer.analyze(sql, databaseCapability);
         ShardingSpherePreconditions.checkState(SupportedMCPStatement.QUERY == explainedStatement.getStatementClass(),
                 () -> new MCPInvalidRequestException("database_gateway_execute_explain_query only supports QUERY statements as the explained SQL."));
         String actualExplainSql = scanner.normalizeSingleStatement(explainSql);
@@ -46,7 +50,7 @@ final class ExplainSQLCandidateValidator {
                 () -> new MCPInvalidRequestException("Executable comments are not supported by the MCP explain query tool."));
         List<SQLStatementToken> tokens = scanner.tokenize(actualExplainSql);
         checkExplainCandidate(tokens, explainedStatement.getNormalizedSql(), actualExplainSql);
-        return new ClassificationResult(SupportedMCPStatement.EXPLAIN, "EXPLAIN", actualExplainSql, "", explainedStatement.getReferencedObjects());
+        return new ClassificationResult(SupportedMCPStatement.EXPLAIN, "EXPLAIN", actualExplainSql, "", explainedStatement.getReferencedObjects(), false);
     }
     
     private void checkExplainCandidate(final List<SQLStatementToken> tokens, final String sql, final String explainSql) {
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacade.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacade.java
index 823202c1fcb..b279b01c4c6 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacade.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacade.java
@@ -58,7 +58,7 @@ public final class MCPSQLExecutionFacade implements MCPFeatureExecutionFacade {
     
     private final MCPJdbcStatementExecutor statementExecutor;
     
-    private final StatementClassifier statementClassifier;
+    private final MCPStatementAnalyzer statementAnalyzer;
     
     private final SQLStatementScanner scanner;
     
@@ -68,21 +68,24 @@ public final class MCPSQLExecutionFacade implements MCPFeatureExecutionFacade {
         this(databaseCapabilityProvider, new MCPSessionExecutionCoordinator(sessionManager),
                 new MCPJdbcTransactionStatementExecutor(sessionManager),
                 new MCPJdbcStatementExecutor(sessionManager.getTransactionResourceManager().getRuntimeDatabases(), sessionManager.getTransactionResourceManager()),
-                new StatementClassifier(), new SQLStatementScanner(), new SQLExecutionTraceFactory());
+                new MCPStatementAnalyzer(), new SQLStatementScanner(), new SQLExecutionTraceFactory());
     }
     
     @Override
     public SQLExecutionResult execute(final SQLExecutionRequest executionRequest) {
         try {
-            return sessionExecutionCoordinator.executeWithSessionLock(executionRequest.getSessionId(), () -> executeInternal(executionRequest, classify(executionRequest)));
+            return sessionExecutionCoordinator.executeWithSessionLock(executionRequest.getSessionId(), () -> {
+                MCPDatabaseCapability databaseCapability = getDatabaseCapability(executionRequest);
+                return executeInternal(executionRequest, classify(executionRequest, databaseCapability), databaseCapability);
+            });
         } catch (final MCPSessionNotExistedException ex) {
             throw recordFailure(executionRequest, SupportedMCPStatement.QUERY.name(), ex);
         }
     }
     
-    private SQLExecutionResult execute(final SQLExecutionRequest executionRequest, final ClassificationResult classificationResult) {
+    private SQLExecutionResult execute(final SQLExecutionRequest executionRequest, final ClassificationResult classificationResult, final MCPDatabaseCapability databaseCapability) {
         try {
-            return sessionExecutionCoordinator.executeWithSessionLock(executionRequest.getSessionId(), () -> executeInternal(executionRequest, classificationResult));
+            return sessionExecutionCoordinator.executeWithSessionLock(executionRequest.getSessionId(), () -> executeInternal(executionRequest, classificationResult, databaseCapability));
         } catch (final MCPSessionNotExistedException ex) {
             throw recordFailure(executionRequest, classificationResult.getTraceStatementMarker(), ex);
         }
@@ -90,9 +93,10 @@ public final class MCPSQLExecutionFacade implements MCPFeatureExecutionFacade {
     
     @Override
     public SQLExecutionResult executeExplain(final SQLExecutionRequest executionRequest, final String sql) {
-        ClassificationResult classificationResult = new ExplainSQLCandidateValidator().validate(sql, executionRequest.getSql());
+        MCPDatabaseCapability databaseCapability = getDatabaseCapability(executionRequest);
+        ClassificationResult classificationResult = new ExplainSQLCandidateValidator(statementAnalyzer).validate(sql, executionRequest.getSql(), databaseCapability);
         try {
-            return execute(executionRequest, classificationResult);
+            return execute(executionRequest, classificationResult, databaseCapability);
         } catch (final MCPInvalidRequestException | MCPQueryFailedException ex) {
             if (hasSQLSyntaxCause(ex)) {
                 throw new ExplainSQLSyntaxException(executionRequest.getDatabase(), executionRequest.getSchema(), sql, executionRequest.getSql(), ex);
@@ -117,9 +121,9 @@ public final class MCPSQLExecutionFacade implements MCPFeatureExecutionFacade {
         return cause instanceof SQLSyntaxErrorException || "37000".equals(sqlState) || "42000".equals(sqlState) || "42601".equals(sqlState);
     }
     
-    private ClassificationResult classify(final SQLExecutionRequest executionRequest) {
+    private ClassificationResult classify(final SQLExecutionRequest executionRequest, final MCPDatabaseCapability databaseCapability) {
         try {
-            return statementClassifier.classify(executionRequest.getSql());
+            return statementAnalyzer.analyze(executionRequest.getSql(), databaseCapability);
         } catch (final MCPUnsupportedException | MCPInvalidRequestException ex) {
             throw recordFailure(executionRequest, SupportedMCPStatement.QUERY.name(), ex);
         } catch (final IllegalArgumentException ex) {
@@ -127,25 +131,29 @@ public final class MCPSQLExecutionFacade implements MCPFeatureExecutionFacade {
         }
     }
     
-    private SQLExecutionResult executeInternal(final SQLExecutionRequest executionRequest, final ClassificationResult classificationResult) {
+    private MCPDatabaseCapability getDatabaseCapability(final SQLExecutionRequest executionRequest) {
         Optional<MCPDatabaseCapability> databaseCapability = databaseCapabilityProvider.provide(executionRequest.getDatabase());
         ShardingSpherePreconditions.checkState(databaseCapability.isPresent(), () -> recordFailure(executionRequest, "QUERY", new DatabaseCapabilityNotFoundException()));
-        MCPDatabaseCapability actualDatabaseCapability = databaseCapability.orElseThrow();
-        ShardingSpherePreconditions.checkContains(actualDatabaseCapability.getSupportedStatementClasses(), classificationResult.getStatementClass(),
+        return databaseCapability.orElseThrow();
+    }
+    
+    private SQLExecutionResult executeInternal(final SQLExecutionRequest executionRequest, final ClassificationResult classificationResult,
+                                               final MCPDatabaseCapability databaseCapability) {
+        ShardingSpherePreconditions.checkContains(databaseCapability.getSupportedStatementClasses(), classificationResult.getStatementClass(),
                 () -> recordFailure(executionRequest, classificationResult.getTraceStatementMarker(), new StatementClassNotSupportedException()));
-        checkCrossSchemaSql(executionRequest, actualDatabaseCapability, classificationResult);
+        checkCrossSchemaSql(executionRequest, databaseCapability, classificationResult);
         try {
             switch (classificationResult.getStatementClass()) {
                 case TRANSACTION_CONTROL:
                 case SAVEPOINT:
                     return recordSuccess(executionRequest, transactionStatementExecutor.execute(
-                            executionRequest.getSessionId(), executionRequest.getDatabase(), actualDatabaseCapability, classificationResult), classificationResult.getTraceStatementMarker());
+                            executionRequest.getSessionId(), executionRequest.getDatabase(), databaseCapability, classificationResult), classificationResult.getTraceStatementMarker());
                 case QUERY:
                 case EXPLAIN:
                 case DML:
                 case DDL:
                 case DCL:
-                    return recordSuccess(executionRequest, statementExecutor.execute(executionRequest, classificationResult, actualDatabaseCapability), classificationResult.getTraceStatementMarker());
+                    return recordSuccess(executionRequest, statementExecutor.execute(executionRequest, classificationResult, databaseCapability), classificationResult.getTraceStatementMarker());
                 default:
                     throw new StatementClassNotSupportedException();
             }
@@ -169,22 +177,22 @@ public final class MCPSQLExecutionFacade implements MCPFeatureExecutionFacade {
         for (SQLStatementObjectName each : classificationResult.getReferencedObjects()) {
             if (isCrossSchemaReference(each, executionRequest.getDatabase(), classificationResult, identifierCasePolicy)) {
                 throw recordFailure(executionRequest, classificationResult.getTraceStatementMarker(), new MCPInvalidRequestException(
-                        String.format("Cross-schema SQL is not supported for database `%s`: `%s`.", executionRequest.getDatabase(), each.objectName())));
+                        String.format("Cross-schema SQL is not supported for database `%s`: `%s`.", executionRequest.getDatabase(), each.getObjectName())));
             }
         }
     }
     
     private boolean isCrossSchemaReference(final SQLStatementObjectName objectName, final String databaseName, final ClassificationResult classificationResult,
                                            final IdentifierCasePolicy identifierCasePolicy) {
-        if (objectName.qualified()) {
-            return !identifierCasePolicy.matches(databaseName, objectName.firstIdentifier(), objectName.firstIdentifierQuoteCharacter());
+        if (objectName.isQualified()) {
+            return !identifierCasePolicy.matches(databaseName, objectName.getFirstIdentifier(), objectName.getFirstIdentifierQuoteCharacter());
         }
         return isDatabaseOrSchemaBoundaryReference(objectName, databaseName, classificationResult, identifierCasePolicy);
     }
     
     private boolean isDatabaseOrSchemaBoundaryReference(final SQLStatementObjectName objectName, final String databaseName, final ClassificationResult classificationResult,
                                                         final IdentifierCasePolicy identifierCasePolicy) {
-        if (identifierCasePolicy.matches(databaseName, objectName.firstIdentifier(), objectName.firstIdentifierQuoteCharacter())) {
+        if (identifierCasePolicy.matches(databaseName, objectName.getFirstIdentifier(), objectName.getFirstIdentifierQuoteCharacter())) {
             return false;
         }
         String actualSql = classificationResult.getNormalizedSql();
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPStatementAnalyzer.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPStatementAnalyzer.java
new file mode 100644
index 00000000000..e37cde39fd8
--- /dev/null
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPStatementAnalyzer.java
@@ -0,0 +1,217 @@
+/*
+ * 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.core.tool.handler.execute;
+
+import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.distsql.statement.type.rdl.rule.database.DatabaseRuleDefinitionStatement;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
+import org.apache.shardingsphere.infra.parser.SQLParserEngine;
+import org.apache.shardingsphere.infra.parser.ShardingSphereSQLParserEngine;
+import org.apache.shardingsphere.infra.spi.ShardingSphereServiceLoader;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
+import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnsupportedException;
+import org.apache.shardingsphere.mcp.core.protocol.exception.MCPUnsupportedSQLStatementException;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
+import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPRuleDistSQLStatementClassifier;
+import org.apache.shardingsphere.sql.parser.engine.api.CacheOption;
+import org.apache.shardingsphere.sql.parser.spi.DialectSQLParserFacade;
+import org.apache.shardingsphere.sql.parser.spi.SQLStatementVisitorFacade;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dcl.GrantStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dcl.RevokeStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.DDLStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.MergeStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class MCPStatementAnalyzer {
+    
+    private static final CacheOption DISABLED_CACHE_OPTION = new CacheOption(0, 0L);
+    
+    private static final Set<String> DDL_STATEMENT_TYPES = Set.of("CREATE", "ALTER", "DROP", "TRUNCATE");
+    
+    private final SQLStatementScanner scanner = new SQLStatementScanner();
+    
+    private final SQLStatementSafetyValidator safetyValidator = new SQLStatementSafetyValidator(scanner);
+    
+    private final SQLStatementObjectExtractor objectExtractor = new SQLStatementObjectExtractor(scanner);
+    
+    private final Collection<MCPRuleDistSQLStatementClassifier> ruleDistSQLStatementClassifiers =
+            ShardingSphereServiceLoader.getServiceInstances(MCPRuleDistSQLStatementClassifier.class);
+    
+    private final Map<String, SQLParserEngine> parserEngines = new ConcurrentHashMap<>();
+    
+    ClassificationResult analyze(final String sql, final MCPDatabaseCapability databaseCapability) {
+        String actualSql = scanner.normalizeSingleStatement(sql);
+        String leadingSql = actualSql.substring(scanner.skipInsignificant(actualSql, 0)).trim();
+        String upperLeadingSql = leadingSql.toUpperCase(Locale.ENGLISH);
+        safetyValidator.checkLeadingStatement(upperLeadingSql, actualSql);
+        if (isSavepointStatement(upperLeadingSql)) {
+            return analyzeSavepointStatement(actualSql, leadingSql, upperLeadingSql);
+        }
+        if (isTransactionControlStatement(upperLeadingSql)) {
+            return new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, extractTransactionStatementType(upperLeadingSql), actualSql, "", Set.of(), false);
+        }
+        SQLStatement sqlStatement = parse(actualSql, databaseCapability.getDatabaseType());
+        safetyValidator.checkParsedStatement(sqlStatement, actualSql);
+        boolean ruleDistSQL = isRuleDistSQL(sqlStatement);
+        String leadingKeyword = scanner.extractLeadingKeyword(actualSql);
+        SupportedMCPStatement statementClass = resolveStatementClass(sqlStatement, leadingKeyword, ruleDistSQL);
+        String statementType = resolveStatementType(sqlStatement, statementClass, leadingKeyword);
+        return new ClassificationResult(statementClass, statementType, actualSql, "", objectExtractor.extract(sqlStatement, actualSql), ruleDistSQL);
+    }
+    
+    private ClassificationResult analyzeSavepointStatement(final String actualSql, final String leadingSql, final String upperLeadingSql) {
+        String savepointName = extractSavepointName(leadingSql);
+        ShardingSpherePreconditions.checkState(!savepointName.isEmpty(), () -> new MCPInvalidRequestException("Savepoint name is required."));
+        return new ClassificationResult(SupportedMCPStatement.SAVEPOINT, extractTransactionStatementType(upperLeadingSql), actualSql, savepointName, Set.of(), false);
+    }
+    
+    private SQLStatement parse(final String sql, final String databaseType) {
+        try {
+            return parserEngines.computeIfAbsent(databaseType, this::createParserEngine).parse(sql, false);
+        } catch (final MCPUnsupportedException ex) {
+            throw ex;
+            // CHECKSTYLE:OFF
+        } catch (final RuntimeException ex) {
+            // CHECKSTYLE:ON
+            throw new MCPUnsupportedSQLStatementException(ex);
+        }
+    }
+    
+    private SQLParserEngine createParserEngine(final String databaseTypeName) {
+        DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, databaseTypeName);
+        ShardingSpherePreconditions.checkState(DatabaseTypedSPILoader.findService(DialectSQLParserFacade.class, databaseType).isPresent()
+                && DatabaseTypedSPILoader.findService(SQLStatementVisitorFacade.class, databaseType).isPresent(),
+                () -> new MCPUnsupportedException(String.format("SQL parser is not available for database type `%s`.", databaseTypeName)));
+        return new ShardingSphereSQLParserEngine(databaseType, DISABLED_CACHE_OPTION, DISABLED_CACHE_OPTION);
+    }
+    
+    private SupportedMCPStatement resolveStatementClass(final SQLStatement sqlStatement, final String leadingKeyword, final boolean ruleDistSQL) {
+        if (ruleDistSQL && DDL_STATEMENT_TYPES.contains(leadingKeyword)) {
+            return SupportedMCPStatement.DDL;
+        }
+        if (sqlStatement instanceof SelectStatement && hasExpectedLeadingKeyword(leadingKeyword, "SELECT")) {
+            return SupportedMCPStatement.QUERY;
+        }
+        if (sqlStatement instanceof InsertStatement && hasExpectedLeadingKeyword(leadingKeyword, "INSERT")
+                || sqlStatement instanceof UpdateStatement && hasExpectedLeadingKeyword(leadingKeyword, "UPDATE")
+                || sqlStatement instanceof DeleteStatement && hasExpectedLeadingKeyword(leadingKeyword, "DELETE")
+                || sqlStatement instanceof MergeStatement && hasExpectedLeadingKeyword(leadingKeyword, "MERGE")) {
+            return SupportedMCPStatement.DML;
+        }
+        if (sqlStatement instanceof DDLStatement && DDL_STATEMENT_TYPES.contains(leadingKeyword)) {
+            return SupportedMCPStatement.DDL;
+        }
+        if (sqlStatement instanceof GrantStatement && "GRANT".equals(leadingKeyword) || sqlStatement instanceof RevokeStatement && "REVOKE".equals(leadingKeyword)) {
+            return SupportedMCPStatement.DCL;
+        }
+        throw new MCPUnsupportedSQLStatementException();
+    }
+    
+    private boolean hasExpectedLeadingKeyword(final String leadingKeyword, final String expectedKeyword) {
+        return expectedKeyword.equals(leadingKeyword) || "WITH".equals(leadingKeyword);
+    }
+    
+    private String resolveStatementType(final SQLStatement sqlStatement, final SupportedMCPStatement statementClass, final String leadingKeyword) {
+        if (SupportedMCPStatement.QUERY == statementClass) {
+            return "SELECT";
+        }
+        if (SupportedMCPStatement.DML == statementClass) {
+            if (sqlStatement instanceof InsertStatement) {
+                return "INSERT";
+            }
+            if (sqlStatement instanceof UpdateStatement) {
+                return "UPDATE";
+            }
+            if (sqlStatement instanceof DeleteStatement) {
+                return "DELETE";
+            }
+            return "MERGE";
+        }
+        return leadingKeyword;
+    }
+    
+    private boolean isRuleDistSQL(final SQLStatement sqlStatement) {
+        if (sqlStatement instanceof DatabaseRuleDefinitionStatement) {
+            return true;
+        }
+        for (MCPRuleDistSQLStatementClassifier each : ruleDistSQLStatementClassifiers) {
+            if (each.isRuleDistSQL(sqlStatement)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private boolean isTransactionControlStatement(final String upperSql) {
+        return "BEGIN".equals(upperSql) || "START TRANSACTION".equals(upperSql) || "COMMIT".equals(upperSql) || "ROLLBACK".equals(upperSql);
+    }
+    
+    private boolean isSavepointStatement(final String upperSql) {
+        return "SAVEPOINT".equals(upperSql) || upperSql.startsWith("SAVEPOINT ")
+                || "ROLLBACK TO".equals(upperSql) || upperSql.startsWith("ROLLBACK TO ")
+                || "RELEASE SAVEPOINT".equals(upperSql) || upperSql.startsWith("RELEASE SAVEPOINT ");
+    }
+    
+    private String extractTransactionStatementType(final String upperSql) {
+        if (upperSql.startsWith("START TRANSACTION")) {
+            return "START TRANSACTION";
+        }
+        if (upperSql.startsWith("ROLLBACK TO SAVEPOINT")) {
+            return "ROLLBACK TO SAVEPOINT";
+        }
+        if (upperSql.startsWith("ROLLBACK TO")) {
+            return "ROLLBACK TO";
+        }
+        if (upperSql.startsWith("RELEASE SAVEPOINT")) {
+            return "RELEASE SAVEPOINT";
+        }
+        return upperSql.split("\\s+")[0];
+    }
+    
+    private String extractSavepointName(final String sql) {
+        String[] tokens = sql.split("\\s+");
+        if ("SAVEPOINT".equalsIgnoreCase(tokens[0]) && 2 == tokens.length) {
+            return tokens[1];
+        }
+        if ("RELEASE".equalsIgnoreCase(tokens[0]) && 3 == tokens.length && "SAVEPOINT".equalsIgnoreCase(tokens[1])) {
+            return tokens[2];
+        }
+        if ("ROLLBACK".equalsIgnoreCase(tokens[0]) && tokens.length >= 3 && "TO".equalsIgnoreCase(tokens[1])) {
+            if (3 == tokens.length && !"SAVEPOINT".equalsIgnoreCase(tokens[2])) {
+                return tokens[2];
+            }
+            if (4 == tokens.length && "SAVEPOINT".equalsIgnoreCase(tokens[2])) {
+                return tokens[3];
+            }
+        }
+        return "";
+    }
+}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java
index 69de789909d..7b8845b0248 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLExecutionToolHandlerSupport.java
@@ -24,7 +24,9 @@ import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestExc
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPInvalidToolArgumentException;
 import org.apache.shardingsphere.mcp.core.tool.request.MCPToolArguments;
 import org.apache.shardingsphere.mcp.support.database.MCPDatabaseRequestContext;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
+import org.apache.shardingsphere.mcp.support.database.exception.DatabaseCapabilityNotFoundException;
 import org.apache.shardingsphere.mcp.support.database.tool.request.SQLExecutionRequest;
 import org.apache.shardingsphere.mcp.support.security.MCPRuntimeProtectionPolicy;
 
@@ -34,10 +36,18 @@ import java.util.Map;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 final class SQLExecutionToolHandlerSupport {
     
+    private static final MCPStatementAnalyzer STATEMENT_ANALYZER = new MCPStatementAnalyzer();
+    
     static boolean isQueryStatement(final ClassificationResult classificationResult) {
         return SupportedMCPStatement.QUERY == classificationResult.getStatementClass();
     }
     
+    static ClassificationResult analyze(final MCPDatabaseRequestContext databaseContext, final MCPToolArguments toolArguments, final String sql) {
+        String database = toolArguments.getStringArgument("database");
+        MCPDatabaseCapability databaseCapability = databaseContext.getCapabilityFacade().provide(database).orElseThrow(DatabaseCapabilityNotFoundException::new);
+        return STATEMENT_ANALYZER.analyze(sql, databaseCapability);
+    }
+    
     static void checkExecutionArguments(final MCPToolArguments toolArguments, final String sourceTool) {
         resolveMaxRows(toolArguments, sourceTool);
         getIntegerArgument(toolArguments, sourceTool, "timeout_ms", MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS, MCPRuntimeProtectionPolicy.DEFAULT_TIMEOUT_MILLISECONDS,
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementClassResolver.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementClassResolver.java
deleted file mode 100644
index 7f607e4bde2..00000000000
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementClassResolver.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.core.tool.handler.execute;
-
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPUnsupportedSQLStatementException;
-import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
-
-final class SQLStatementClassResolver {
-    
-    SupportedMCPStatement resolve(final SQLStatementStructure statementStructure) {
-        String statementType = statementStructure.statementType();
-        if ("SELECT".equals(statementType)) {
-            return statementStructure.containsDataModifyingCommonTableExpression() ? SupportedMCPStatement.DML : SupportedMCPStatement.QUERY;
-        }
-        if ("INSERT".equals(statementType) || "UPDATE".equals(statementType) || "DELETE".equals(statementType) || "MERGE".equals(statementType)) {
-            return SupportedMCPStatement.DML;
-        }
-        if ("CREATE".equals(statementType) || "ALTER".equals(statementType) || "DROP".equals(statementType) || "TRUNCATE".equals(statementType)) {
-            return SupportedMCPStatement.DDL;
-        }
-        if ("GRANT".equals(statementType) || "REVOKE".equals(statementType)) {
-            return SupportedMCPStatement.DCL;
-        }
-        throw new MCPUnsupportedSQLStatementException();
-    }
-}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectExtractor.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectExtractor.java
new file mode 100644
index 00000000000..5d159a84cb5
--- /dev/null
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectExtractor.java
@@ -0,0 +1,327 @@
+/*
+ * 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.core.tool.handler.execute;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.sql.parser.statement.core.extractor.TableExtractor;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.ddl.index.IndexSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.complex.CommonTableExpressionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.WithSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.DeleteMultiTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.JoinTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SimpleTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SubqueryTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.TableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.type.TableSQLStatementAttribute;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dcl.GrantStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dcl.RevokeStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.AlterDatabaseStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.CreateDatabaseStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.DropDatabaseStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.index.AlterIndexStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.index.CreateIndexStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.index.DropIndexStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.schema.AlterSchemaStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.schema.CreateSchemaStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.schema.DropSchemaStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.sequence.AlterSequenceStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.sequence.CreateSequenceStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.sequence.DropSequenceStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.table.AlterTableStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.table.CreateTableStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.view.AlterViewStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.view.CreateViewStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.MergeStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+final class SQLStatementObjectExtractor {
+    
+    private static final Set<String> DCL_OBJECT_TYPE_KEYWORDS = Set.of("TABLE", "VIEW", "INDEX", "SEQUENCE", "DATABASE", "SCHEMA", "FUNCTION", "PROCEDURE");
+    
+    private final SQLStatementScanner scanner;
+    
+    Collection<SQLStatementObjectName> extract(final SQLStatement sqlStatement, final String sql) {
+        Collection<SQLStatementObjectName> result = new LinkedHashSet<>();
+        extractDirectTargets(sqlStatement, result);
+        TableExtractor tableExtractor = new TableExtractor();
+        tableExtractor.extractTablesFromSQLStatement(sqlStatement);
+        addTableSegments(tableExtractor.getTableContext(), result);
+        sqlStatement.getAttributes().findAttribute(TableSQLStatementAttribute.class).ifPresent(attribute -> addTables(attribute.getTables(), result));
+        extractMergeTables(sqlStatement, result);
+        extractCommonTableExpressionTables(sqlStatement, result);
+        removeCommonTableExpressionAliases(sqlStatement, result);
+        List<SQLStatementToken> tokens = scanner.tokenize(sql);
+        extractDCLTarget(sqlStatement, tokens, result);
+        extractQualifiedFunctions(tokens, result);
+        return result;
+    }
+    
+    private void extractDCLTarget(final SQLStatement sqlStatement, final List<SQLStatementToken> tokens, final Collection<SQLStatementObjectName> result) {
+        if (!(sqlStatement instanceof GrantStatement) && !(sqlStatement instanceof RevokeStatement)) {
+            return;
+        }
+        for (int index = 0; index < tokens.size(); index++) {
+            if (scanner.isKeyword(tokens.get(index), "ON")) {
+                int objectStartIndex = skipDCLObjectType(tokens, index + 1);
+                addObjectName(tokens, objectStartIndex, findObjectNameEnd(tokens, objectStartIndex), result);
+                return;
+            }
+        }
+    }
+    
+    private int skipDCLObjectType(final List<SQLStatementToken> tokens, final int startIndex) {
+        return startIndex < tokens.size() && DCL_OBJECT_TYPE_KEYWORDS.contains(tokens.get(startIndex).upperText()) ? startIndex + 1 : startIndex;
+    }
+    
+    private void extractQualifiedFunctions(final List<SQLStatementToken> tokens, final Collection<SQLStatementObjectName> result) {
+        int index = 0;
+        while (index < tokens.size()) {
+            int objectNameEnd = findObjectNameEnd(tokens, index);
+            if (objectNameEnd - index > 1 && objectNameEnd < tokens.size() && "(".equals(tokens.get(objectNameEnd).text())) {
+                addObjectName(tokens, index, objectNameEnd, result);
+                index = objectNameEnd + 1;
+            } else {
+                index++;
+            }
+        }
+    }
+    
+    private int findObjectNameEnd(final List<SQLStatementToken> tokens, final int startIndex) {
+        if (startIndex >= tokens.size() || !isObjectNameToken(tokens.get(startIndex))) {
+            return startIndex;
+        }
+        int result = startIndex + 1;
+        while (result + 1 < tokens.size() && ".".equals(tokens.get(result).text()) && isObjectNameToken(tokens.get(result + 1))) {
+            result += 2;
+        }
+        return result;
+    }
+    
+    private boolean isObjectNameToken(final SQLStatementToken token) {
+        return token.identifier() || "*".equals(token.text());
+    }
+    
+    private void addObjectName(final List<SQLStatementToken> tokens, final int startIndex, final int stopIndex, final Collection<SQLStatementObjectName> result) {
+        if (startIndex >= stopIndex) {
+            return;
+        }
+        List<IdentifierValue> identifiers = new LinkedList<>();
+        for (int index = startIndex; index < stopIndex; index += 2) {
+            identifiers.add(new IdentifierValue(tokens.get(index).text()));
+        }
+        result.add(SQLStatementObjectName.from(identifiers));
+    }
+    
+    private void extractDirectTargets(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> result) {
+        if (sqlStatement instanceof InsertStatement) {
+            ((InsertStatement) sqlStatement).getTable().ifPresent(optional -> addTable(optional, result));
+        } else if (sqlStatement instanceof UpdateStatement) {
+            addTableSegment(((UpdateStatement) sqlStatement).getTable(), result);
+        } else if (sqlStatement instanceof DeleteStatement) {
+            addTableSegment(((DeleteStatement) sqlStatement).getTable(), result);
+        } else if (sqlStatement instanceof MergeStatement) {
+            addTableSegment(((MergeStatement) sqlStatement).getTarget(), result);
+        } else if (sqlStatement instanceof CreateTableStatement) {
+            CreateTableStatement createTableStatement = (CreateTableStatement) sqlStatement;
+            addTable(createTableStatement.getTable(), result);
+            createTableStatement.getLikeTable().ifPresent(table -> addTable(table, result));
+            createTableStatement.getSelectStatement().ifPresent(select -> addSelectTables(select, result));
+        } else if (sqlStatement instanceof AlterTableStatement) {
+            addTable(((AlterTableStatement) sqlStatement).getTable(), result);
+        } else if (sqlStatement instanceof CreateViewStatement) {
+            addTable(((CreateViewStatement) sqlStatement).getView(), result);
+        } else if (sqlStatement instanceof AlterViewStatement) {
+            AlterViewStatement alterViewStatement = (AlterViewStatement) sqlStatement;
+            addTable(alterViewStatement.getView(), result);
+            alterViewStatement.getRenameView().ifPresent(view -> addTable(view, result));
+            alterViewStatement.getSelect().ifPresent(select -> addSelectTables(select, result));
+        }
+        extractIndexTargets(sqlStatement, result);
+        extractDatabaseTargets(sqlStatement, result);
+        extractSchemaTargets(sqlStatement, result);
+        extractSequenceTargets(sqlStatement, result);
+    }
+    
+    private void extractIndexTargets(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> result) {
+        if (sqlStatement instanceof CreateIndexStatement) {
+            addIndex(((CreateIndexStatement) sqlStatement).getIndex(), result);
+        } else if (sqlStatement instanceof AlterIndexStatement) {
+            ((AlterIndexStatement) sqlStatement).getIndex().ifPresent(index -> addIndex(index, result));
+            ((AlterIndexStatement) sqlStatement).getRenameIndex().ifPresent(index -> addIndex(index, result));
+        } else if (sqlStatement instanceof DropIndexStatement) {
+            for (IndexSegment each : ((DropIndexStatement) sqlStatement).getIndexes()) {
+                addIndex(each, result);
+            }
+        }
+    }
+    
+    private void extractDatabaseTargets(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> result) {
+        if (sqlStatement instanceof CreateDatabaseStatement) {
+            addName(((CreateDatabaseStatement) sqlStatement).getDatabaseName(), result);
+        } else if (sqlStatement instanceof AlterDatabaseStatement) {
+            addName(((AlterDatabaseStatement) sqlStatement).getDatabaseName(), result);
+            ((AlterDatabaseStatement) sqlStatement).getRenameDatabaseName().ifPresent(name -> addName(name, result));
+        } else if (sqlStatement instanceof DropDatabaseStatement) {
+            addName(((DropDatabaseStatement) sqlStatement).getDatabaseName(), result);
+        }
+    }
+    
+    private void extractSchemaTargets(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> result) {
+        if (sqlStatement instanceof CreateSchemaStatement) {
+            ((CreateSchemaStatement) sqlStatement).getSchemaName().ifPresent(schema -> addIdentifier(schema, result));
+        } else if (sqlStatement instanceof AlterSchemaStatement) {
+            addIdentifier(((AlterSchemaStatement) sqlStatement).getSchemaName(), result);
+            ((AlterSchemaStatement) sqlStatement).getRenameSchema().ifPresent(schema -> addIdentifier(schema, result));
+        } else if (sqlStatement instanceof DropSchemaStatement) {
+            for (IdentifierValue each : ((DropSchemaStatement) sqlStatement).getSchemaNames()) {
+                addIdentifier(each, result);
+            }
+        }
+    }
+    
+    private void extractSequenceTargets(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> result) {
+        if (sqlStatement instanceof CreateSequenceStatement) {
+            addName(((CreateSequenceStatement) sqlStatement).getSequenceName(), result);
+        } else if (sqlStatement instanceof AlterSequenceStatement) {
+            addName(((AlterSequenceStatement) sqlStatement).getSequenceName(), result);
+        } else if (sqlStatement instanceof DropSequenceStatement) {
+            for (String each : ((DropSequenceStatement) sqlStatement).getSequenceNames()) {
+                addName(each, result);
+            }
+        }
+    }
+    
+    private void extractMergeTables(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> result) {
+        if (sqlStatement instanceof MergeStatement) {
+            addTableSegment(((MergeStatement) sqlStatement).getSource(), result);
+        }
+    }
+    
+    private void extractCommonTableExpressionTables(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> result) {
+        findWithSegment(sqlStatement).ifPresent(with -> {
+            for (CommonTableExpressionSegment each : with.getCommonTableExpressions()) {
+                TableExtractor extractor = new TableExtractor();
+                extractor.extractTablesFromSelect(each.getSubquery().getSelect());
+                addTableSegments(extractor.getTableContext(), result);
+            }
+        });
+    }
+    
+    private void addSelectTables(final SelectStatement selectStatement, final Collection<SQLStatementObjectName> result) {
+        TableExtractor extractor = new TableExtractor();
+        extractor.extractTablesFromSelect(selectStatement);
+        addTableSegments(extractor.getTableContext(), result);
+    }
+    
+    private void removeCommonTableExpressionAliases(final SQLStatement sqlStatement, final Collection<SQLStatementObjectName> objectNames) {
+        Set<String> aliases = new HashSet<>();
+        collectCommonTableExpressionAliases(sqlStatement, aliases);
+        objectNames.removeIf(each -> !each.isQualified() && aliases.contains(each.getObjectName().toUpperCase(Locale.ENGLISH)));
+    }
+    
+    private void collectCommonTableExpressionAliases(final SQLStatement sqlStatement, final Collection<String> result) {
+        findWithSegment(sqlStatement).ifPresent(with -> {
+            for (CommonTableExpressionSegment each : with.getCommonTableExpressions()) {
+                each.getAliasName().ifPresent(alias -> result.add(alias.toUpperCase(Locale.ENGLISH)));
+                collectCommonTableExpressionAliases(each.getSubquery().getSelect(), result);
+            }
+        });
+    }
+    
+    private Optional<WithSegment> findWithSegment(final SQLStatement sqlStatement) {
+        if (sqlStatement instanceof SelectStatement) {
+            return ((SelectStatement) sqlStatement).getWith();
+        }
+        if (sqlStatement instanceof InsertStatement) {
+            return ((InsertStatement) sqlStatement).getWith();
+        }
+        if (sqlStatement instanceof UpdateStatement) {
+            return ((UpdateStatement) sqlStatement).getWith();
+        }
+        if (sqlStatement instanceof DeleteStatement) {
+            return ((DeleteStatement) sqlStatement).getWith();
+        }
+        return sqlStatement instanceof MergeStatement ? ((MergeStatement) sqlStatement).getWith() : Optional.empty();
+    }
+    
+    private void addTableSegment(final TableSegment tableSegment, final Collection<SQLStatementObjectName> result) {
+        if (tableSegment instanceof SimpleTableSegment) {
+            addTable((SimpleTableSegment) tableSegment, result);
+        } else if (tableSegment instanceof JoinTableSegment) {
+            addTableSegment(((JoinTableSegment) tableSegment).getLeft(), result);
+            addTableSegment(((JoinTableSegment) tableSegment).getRight(), result);
+        } else if (tableSegment instanceof SubqueryTableSegment) {
+            TableExtractor extractor = new TableExtractor();
+            extractor.extractTablesFromSelect(((SubqueryTableSegment) tableSegment).getSubquery().getSelect());
+            addTableSegments(extractor.getTableContext(), result);
+        } else if (tableSegment instanceof DeleteMultiTableSegment) {
+            addTables(((DeleteMultiTableSegment) tableSegment).getActualDeleteTables(), result);
+            addTableSegment(((DeleteMultiTableSegment) tableSegment).getRelationTable(), result);
+        }
+    }
+    
+    private void addTableSegments(final Collection<TableSegment> tableSegments, final Collection<SQLStatementObjectName> result) {
+        for (TableSegment each : tableSegments) {
+            addTableSegment(each, result);
+        }
+    }
+    
+    private void addTables(final Collection<SimpleTableSegment> tables, final Collection<SQLStatementObjectName> result) {
+        for (SimpleTableSegment each : tables) {
+            addTable(each, result);
+        }
+    }
+    
+    private void addTable(final SimpleTableSegment table, final Collection<SQLStatementObjectName> result) {
+        if (null != table) {
+            result.add(SQLStatementObjectName.from(table.getOwner(), table.getTableName().getIdentifier()));
+        }
+    }
+    
+    private void addIndex(final IndexSegment index, final Collection<SQLStatementObjectName> result) {
+        result.add(SQLStatementObjectName.from(index.getOwner(), index.getIndexName().getIdentifier()));
+    }
+    
+    private void addName(final String name, final Collection<SQLStatementObjectName> result) {
+        if (null != name && !name.isEmpty()) {
+            result.add(SQLStatementObjectName.fromNormalizedName(name));
+        }
+    }
+    
+    private void addIdentifier(final IdentifierValue identifier, final Collection<SQLStatementObjectName> result) {
+        if (null != identifier) {
+            result.add(SQLStatementObjectName.from(Optional.empty(), identifier));
+        }
+    }
+}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectName.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectName.java
index 0f8f97f4bc7..29c28a2bf27 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectName.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectName.java
@@ -18,12 +18,22 @@
 package org.apache.shardingsphere.mcp.core.tool.handler.execute;
 
 import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
 import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OwnerSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
 
-@AllArgsConstructor(access = AccessLevel.PACKAGE)
-@EqualsAndHashCode(exclude = "nextIndex")
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+@EqualsAndHashCode
+@Getter
 final class SQLStatementObjectName {
     
     private final String objectName;
@@ -34,31 +44,27 @@ final class SQLStatementObjectName {
     
     private final boolean qualified;
     
-    private final int nextIndex;
-    
     static SQLStatementObjectName fromNormalizedName(final String objectName) {
         int qualifierSeparatorIndex = objectName.indexOf('.');
         return new SQLStatementObjectName(objectName, -1 == qualifierSeparatorIndex ? objectName : objectName.substring(0, qualifierSeparatorIndex),
-                QuoteCharacter.NONE, -1 != qualifierSeparatorIndex, 0);
-    }
-    
-    String objectName() {
-        return objectName;
-    }
-    
-    String firstIdentifier() {
-        return firstIdentifier;
+                QuoteCharacter.NONE, -1 != qualifierSeparatorIndex);
     }
     
-    QuoteCharacter firstIdentifierQuoteCharacter() {
-        return firstIdentifierQuoteCharacter;
+    static SQLStatementObjectName from(final Optional<OwnerSegment> owner, final IdentifierValue identifier) {
+        List<IdentifierValue> identifiers = new LinkedList<>();
+        owner.ifPresent(optional -> addOwnerIdentifiers(optional, identifiers));
+        identifiers.add(identifier);
+        return from(identifiers);
     }
     
-    boolean qualified() {
-        return qualified;
+    static SQLStatementObjectName from(final List<IdentifierValue> identifiers) {
+        IdentifierValue firstIdentifier = identifiers.get(0);
+        return new SQLStatementObjectName(identifiers.stream().map(IdentifierValue::getValue).collect(Collectors.joining(".")),
+                firstIdentifier.getValue(), firstIdentifier.getQuoteCharacter(), 1 < identifiers.size());
     }
     
-    int nextIndex() {
-        return nextIndex;
+    private static void addOwnerIdentifiers(final OwnerSegment owner, final Collection<IdentifierValue> result) {
+        owner.getOwner().ifPresent(optional -> addOwnerIdentifiers(optional, result));
+        result.add(owner.getIdentifier());
     }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectNameReader.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectNameReader.java
deleted file mode 100644
index 0a8b64938c1..00000000000
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementObjectNameReader.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.core.tool.handler.execute;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
-
-import java.util.List;
-
-@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
-final class SQLStatementObjectNameReader {
-    
-    private static final List<String> OBJECT_LIST_STOP_KEYWORDS = List.of("JOIN", "WHERE", "ON", "USING", "SET", "VALUES", "RETURNING", "GROUP", "ORDER", "HAVING", "LIMIT", "OFFSET",
-            "FETCH", "UNION", "EXCEPT", "INTERSECT", "WHEN", "FROM", "TO");
-    
-    private final SQLStatementScanner scanner;
-    
-    SQLStatementObjectName readObjectName(final List<SQLStatementToken> tokens, final int startIndex, final String... optionalKeywords) {
-        int currentIndex = startIndex;
-        while (currentIndex < tokens.size()) {
-            SQLStatementToken token = tokens.get(currentIndex);
-            if (scanner.isKeyword(token, optionalKeywords)) {
-                currentIndex++;
-                continue;
-            }
-            if ("(".equals(token.text())) {
-                return new SQLStatementObjectName("", "", QuoteCharacter.NONE, false, currentIndex);
-            }
-            return readQualifiedName(tokens, currentIndex);
-        }
-        return new SQLStatementObjectName("", "", QuoteCharacter.NONE, false, currentIndex);
-    }
-    
-    SQLStatementObjectName readQualifiedName(final List<SQLStatementToken> tokens, final int startIndex) {
-        if (startIndex >= tokens.size() || !isObjectNameSegment(tokens.get(startIndex))) {
-            return new SQLStatementObjectName("", "", QuoteCharacter.NONE, false, startIndex);
-        }
-        SQLStatementToken firstToken = tokens.get(startIndex);
-        String firstIdentifier = scanner.normalizeIdentifier(firstToken.text());
-        StringBuilder result = new StringBuilder(firstIdentifier);
-        int currentIndex = startIndex + 1;
-        boolean qualified = false;
-        while (currentIndex + 1 < tokens.size() && ".".equals(tokens.get(currentIndex).text()) && isObjectNameSegment(tokens.get(currentIndex + 1))) {
-            result.append('.').append(scanner.normalizeIdentifier(tokens.get(currentIndex + 1).text()));
-            currentIndex += 2;
-            qualified = true;
-        }
-        return new SQLStatementObjectName(result.toString(), firstIdentifier, QuoteCharacter.getQuoteCharacter(firstToken.text()), qualified, currentIndex);
-    }
-    
-    int skipObjectTail(final List<SQLStatementToken> tokens, final int startIndex) {
-        int result = startIndex;
-        int parenthesesDepth = 0;
-        while (result < tokens.size()) {
-            SQLStatementToken token = tokens.get(result);
-            if ("(".equals(token.text())) {
-                parenthesesDepth++;
-                result++;
-                continue;
-            }
-            if (")".equals(token.text())) {
-                parenthesesDepth--;
-                result++;
-                continue;
-            }
-            if (0 == parenthesesDepth && (",".equals(token.text()) || OBJECT_LIST_STOP_KEYWORDS.contains(token.upperText()))) {
-                return result;
-            }
-            result++;
-        }
-        return result;
-    }
-    
-    private boolean isObjectNameSegment(final SQLStatementToken token) {
-        return token.identifier() || "*".equals(token.text());
-    }
-}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementSafetyValidator.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementSafetyValidator.java
index 82b6c98f3a0..42cc4617b3c 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementSafetyValidator.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementSafetyValidator.java
@@ -21,9 +21,17 @@ import lombok.AccessLevel;
 import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPBannedSQLStatementException;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPLockingReadStatementException;
-import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.complex.CommonTableExpressionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.WithSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.MergeStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
 
 import java.util.List;
+import java.util.Optional;
 
 @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
 final class SQLStatementSafetyValidator {
@@ -47,16 +55,44 @@ final class SQLStatementSafetyValidator {
         }
     }
     
-    void checkStructuredStatement(final SupportedMCPStatement statementClass, final SQLStatementStructure statementStructure) {
-        checkSideEffectingSelectInto(statementStructure);
-        checkLockingRead(statementClass, scanner.tokenize(statementStructure.mainSql()));
-        for (SQLCommonTableExpression each : statementStructure.commonTableExpressions()) {
-            checkStructuredStatement(resolveCommonTableExpressionStatementClass(each.statementStructure()), each.statementStructure());
+    void checkParsedStatement(final SQLStatement sqlStatement, final String sql) {
+        if (sqlStatement instanceof SelectStatement || findWithSegment(sqlStatement).isPresent()) {
+            checkLockingRead(scanner.tokenize(sql));
         }
+        checkParsedStatementTree(sqlStatement);
     }
     
-    private SupportedMCPStatement resolveCommonTableExpressionStatementClass(final SQLStatementStructure statementStructure) {
-        return "SELECT".equals(statementStructure.statementType()) && !statementStructure.containsDataModifyingCommonTableExpression() ? SupportedMCPStatement.QUERY : SupportedMCPStatement.DML;
+    private void checkParsedStatementTree(final SQLStatement sqlStatement) {
+        if (sqlStatement instanceof SelectStatement) {
+            SelectStatement selectStatement = (SelectStatement) sqlStatement;
+            if (selectStatement.getInto().isPresent() || selectStatement.getOutfile().isPresent()) {
+                throw new MCPBannedSQLStatementException();
+            }
+            if (selectStatement.getLock().isPresent()) {
+                throw new MCPLockingReadStatementException();
+            }
+        }
+        findWithSegment(sqlStatement).ifPresent(with -> {
+            for (CommonTableExpressionSegment each : with.getCommonTableExpressions()) {
+                checkParsedStatementTree(each.getSubquery().getSelect());
+            }
+        });
+    }
+    
+    private Optional<WithSegment> findWithSegment(final SQLStatement sqlStatement) {
+        if (sqlStatement instanceof SelectStatement) {
+            return ((SelectStatement) sqlStatement).getWith();
+        }
+        if (sqlStatement instanceof InsertStatement) {
+            return ((InsertStatement) sqlStatement).getWith();
+        }
+        if (sqlStatement instanceof UpdateStatement) {
+            return ((UpdateStatement) sqlStatement).getWith();
+        }
+        if (sqlStatement instanceof DeleteStatement) {
+            return ((DeleteStatement) sqlStatement).getWith();
+        }
+        return sqlStatement instanceof MergeStatement ? ((MergeStatement) sqlStatement).getWith() : Optional.empty();
     }
     
     private boolean isBannedCommand(final String upperSql, final String sql) {
@@ -169,43 +205,12 @@ final class SQLStatementSafetyValidator {
         return true;
     }
     
-    private void checkLockingRead(final SupportedMCPStatement statementClass, final List<SQLStatementToken> tokens) {
-        if (SupportedMCPStatement.QUERY == statementClass && containsLockingReadClause(tokens)) {
-            throw new MCPLockingReadStatementException();
-        }
-    }
-    
-    private void checkSideEffectingSelectInto(final SQLStatementStructure statementStructure) {
-        if ("SELECT".equals(statementStructure.statementType()) && containsTopLevelKeyword(scanner.tokenize(statementStructure.mainSql()), "INTO")) {
-            throw new MCPBannedSQLStatementException();
-        }
-    }
-    
-    private boolean containsLockingReadClause(final List<SQLStatementToken> tokens) {
+    private void checkLockingRead(final List<SQLStatementToken> tokens) {
         for (int index = 0; index < tokens.size(); index++) {
             if (containsLockingReadForClause(tokens, index) || containsLockInShareModeClause(tokens, index)) {
-                return true;
-            }
-        }
-        return false;
-    }
-    
-    private boolean containsTopLevelKeyword(final List<SQLStatementToken> tokens, final String keyword) {
-        int parenthesesDepth = 0;
-        for (SQLStatementToken each : tokens) {
-            if ("(".equals(each.text())) {
-                parenthesesDepth++;
-                continue;
-            }
-            if (")".equals(each.text())) {
-                parenthesesDepth--;
-                continue;
-            }
-            if (0 == parenthesesDepth && scanner.isKeyword(each, keyword)) {
-                return true;
+                throw new MCPLockingReadStatementException();
             }
         }
-        return false;
     }
     
     private boolean containsLockingReadForClause(final List<SQLStatementToken> tokens, final int index) {
@@ -242,15 +247,6 @@ final class SQLStatementSafetyValidator {
         if (upperSql.startsWith("SHOW SINGLE TABLE")) {
             return "SHOW SINGLE TABLE";
         }
-        if (upperSql.startsWith("START TRANSACTION")) {
-            return "START TRANSACTION";
-        }
-        if (upperSql.startsWith("ROLLBACK TO SAVEPOINT")) {
-            return "ROLLBACK TO SAVEPOINT";
-        }
-        if (upperSql.startsWith("RELEASE SAVEPOINT")) {
-            return "RELEASE SAVEPOINT";
-        }
         return upperSql.split("\\s+")[0];
     }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementScanner.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementScanner.java
index 372645ce74c..d65d2134d4b 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementScanner.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementScanner.java
@@ -75,77 +75,6 @@ final class SQLStatementScanner {
         return sql.substring(startIndex, stopIndex).toUpperCase(Locale.ENGLISH);
     }
     
-    boolean matchesKeyword(final String sql, final int startIndex, final String keyword) {
-        int keywordLength = keyword.length();
-        if (startIndex + keywordLength > sql.length() || !sql.regionMatches(true, startIndex, keyword, 0, keywordLength)) {
-            return false;
-        }
-        return startIndex + keywordLength == sql.length() || !isIdentifierCharacter(sql.charAt(startIndex + keywordLength));
-    }
-    
-    int skipKeyword(final String sql, final int startIndex, final String keyword) {
-        if (!matchesKeyword(sql, startIndex, keyword)) {
-            throw new MCPUnsupportedSQLStatementException();
-        }
-        return startIndex + keyword.length();
-    }
-    
-    int skipIdentifier(final String sql, final int startIndex) {
-        if (startIndex >= sql.length()) {
-            throw new MCPUnsupportedSQLStatementException();
-        }
-        char currentChar = sql.charAt(startIndex);
-        if (isQuotedIdentifierStart(currentChar)) {
-            return skipQuotedText(sql, startIndex) + 1;
-        }
-        int result = startIndex;
-        while (result < sql.length()) {
-            currentChar = sql.charAt(result);
-            if (Character.isWhitespace(currentChar) || '(' == currentChar || ',' == currentChar) {
-                break;
-            }
-            result++;
-        }
-        return result;
-    }
-    
-    int skipParenthesizedSegment(final String sql, final int startIndex) {
-        return skipInsignificant(sql, findClosingParenthesis(sql, startIndex) + 1);
-    }
-    
-    int findClosingParenthesis(final String sql, final int startIndex) {
-        int parenthesesDepth = 0;
-        int result = startIndex;
-        while (result < sql.length()) {
-            char currentChar = sql.charAt(result);
-            if (isLineCommentStart(sql, result)) {
-                result = skipLineComment(sql, result) + 1;
-                continue;
-            }
-            if (isBlockCommentStart(sql, result)) {
-                result = skipBlockComment(sql, result) + 1;
-                continue;
-            }
-            if (isQuotedTextStart(currentChar)) {
-                result = skipQuotedText(sql, result) + 1;
-                continue;
-            }
-            if ('(' == currentChar) {
-                parenthesesDepth++;
-                result++;
-                continue;
-            }
-            if (')' == currentChar) {
-                parenthesesDepth--;
-                if (0 == parenthesesDepth) {
-                    return result;
-                }
-            }
-            result++;
-        }
-        throw new MCPUnsupportedSQLStatementException();
-    }
-    
     List<SQLStatementToken> tokenize(final String sql) {
         List<SQLStatementToken> result = new ArrayList<>(Math.max(1, sql.length() / 4));
         int currentIndex = 0;
@@ -195,23 +124,6 @@ final class SQLStatementScanner {
         return !token.quotedIdentifier() && token.upperText().equals(keyword);
     }
     
-    String normalizeIdentifier(final String identifier) {
-        if (identifier.length() >= 2 && '"' == identifier.charAt(0) && '"' == identifier.charAt(identifier.length() - 1)) {
-            return identifier.substring(1, identifier.length() - 1).replace("\"\"", "\"");
-        }
-        if (identifier.length() >= 2 && '`' == identifier.charAt(0) && '`' == identifier.charAt(identifier.length() - 1)) {
-            return identifier.substring(1, identifier.length() - 1).replace("``", "`");
-        }
-        if (identifier.length() >= 2 && '[' == identifier.charAt(0) && ']' == identifier.charAt(identifier.length() - 1)) {
-            return identifier.substring(1, identifier.length() - 1).replace("]]", "]");
-        }
-        return identifier;
-    }
-    
-    String normalizeIdentifierForComparison(final String identifier) {
-        return normalizeIdentifier(identifier).toUpperCase(Locale.ENGLISH);
-    }
-    
     boolean containsExecutableComment(final String sql) {
         int currentIndex = 0;
         while (currentIndex < sql.length()) {
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementStructure.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementStructure.java
deleted file mode 100644
index 08823c97887..00000000000
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementStructure.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.core.tool.handler.execute;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Collection;
-
-@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
-final class SQLStatementStructure {
-    
-    private final String mainSql;
-    
-    private final String statementType;
-    
-    private final boolean containsDataModifyingCommonTableExpression;
-    
-    private final Collection<SQLCommonTableExpression> commonTableExpressions;
-    
-    String mainSql() {
-        return mainSql;
-    }
-    
-    String statementType() {
-        return statementType;
-    }
-    
-    boolean containsDataModifyingCommonTableExpression() {
-        return containsDataModifyingCommonTableExpression;
-    }
-    
-    Collection<SQLCommonTableExpression> commonTableExpressions() {
-        return commonTableExpressions;
-    }
-}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementStructureResolver.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementStructureResolver.java
deleted file mode 100644
index a96d9c5c022..00000000000
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementStructureResolver.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.core.tool.handler.execute;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPUnsupportedSQLStatementException;
-
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
-final class SQLStatementStructureResolver {
-    
-    private final SQLStatementScanner scanner;
-    
-    SQLStatementStructure resolve(final String sql) {
-        int startIndex = scanner.skipInsignificant(sql, 0);
-        if (!scanner.matchesKeyword(sql, startIndex, "WITH")) {
-            String mainSql = sql.substring(startIndex).trim();
-            return new SQLStatementStructure(mainSql, scanner.extractLeadingKeyword(mainSql), false, List.of());
-        }
-        return resolveWithClause(sql, startIndex);
-    }
-    
-    private SQLStatementStructure resolveWithClause(final String sql, final int startIndex) {
-        int currentIndex = scanner.skipKeyword(sql, startIndex, "WITH");
-        currentIndex = scanner.skipInsignificant(sql, currentIndex);
-        if (scanner.matchesKeyword(sql, currentIndex, "RECURSIVE")) {
-            currentIndex = scanner.skipKeyword(sql, currentIndex, "RECURSIVE");
-        }
-        Collection<SQLCommonTableExpression> commonTableExpressions = new LinkedList<>();
-        while (currentIndex < sql.length()) {
-            CommonTableExpressionResolution commonTableExpression = resolveCommonTableExpression(sql, currentIndex);
-            commonTableExpressions.add(new SQLCommonTableExpression(commonTableExpression.aliasName(), resolve(commonTableExpression.sql())));
-            currentIndex = commonTableExpression.nextIndex();
-            if (currentIndex < sql.length() && ',' == sql.charAt(currentIndex)) {
-                currentIndex++;
-                continue;
-            }
-            break;
-        }
-        String mainSql = sql.substring(scanner.skipInsignificant(sql, currentIndex)).trim();
-        if (mainSql.isEmpty()) {
-            throw new MCPUnsupportedSQLStatementException();
-        }
-        return new SQLStatementStructure(mainSql, scanner.extractLeadingKeyword(mainSql), containsDataModifyingCommonTableExpression(commonTableExpressions), commonTableExpressions);
-    }
-    
-    private CommonTableExpressionResolution resolveCommonTableExpression(final String sql, final int startIndex) {
-        int result = scanner.skipInsignificant(sql, startIndex);
-        result = scanner.skipIdentifier(sql, result);
-        result = scanner.skipInsignificant(sql, result);
-        if (result < sql.length() && '(' == sql.charAt(result)) {
-            result = scanner.skipParenthesizedSegment(sql, result);
-        }
-        result = scanner.skipKeyword(sql, result, "AS");
-        result = scanner.skipInsignificant(sql, result);
-        if (scanner.matchesKeyword(sql, result, "NOT")) {
-            result = scanner.skipKeyword(sql, result, "NOT");
-            result = scanner.skipInsignificant(sql, result);
-        }
-        if (scanner.matchesKeyword(sql, result, "MATERIALIZED")) {
-            result = scanner.skipKeyword(sql, result, "MATERIALIZED");
-            result = scanner.skipInsignificant(sql, result);
-        }
-        if (result >= sql.length() || '(' != sql.charAt(result)) {
-            throw new MCPUnsupportedSQLStatementException();
-        }
-        int stopIndex = scanner.findClosingParenthesis(sql, result);
-        int aliasStartIndex = scanner.skipInsignificant(sql, startIndex);
-        int aliasStopIndex = scanner.skipIdentifier(sql, aliasStartIndex);
-        return new CommonTableExpressionResolution(scanner.normalizeIdentifier(sql.substring(aliasStartIndex, aliasStopIndex)),
-                sql.substring(result + 1, stopIndex).trim(), scanner.skipInsignificant(sql, stopIndex + 1));
-    }
-    
-    private boolean containsDataModifyingCommonTableExpression(final Collection<SQLCommonTableExpression> commonTableExpressions) {
-        for (SQLCommonTableExpression each : commonTableExpressions) {
-            if (isDataModifyingStatement(each.statementStructure().statementType()) || each.statementStructure().containsDataModifyingCommonTableExpression()) {
-                return true;
-            }
-        }
-        return false;
-    }
-    
-    private boolean isDataModifyingStatement(final String statementType) {
-        return "INSERT".equals(statementType) || "UPDATE".equals(statementType) || "DELETE".equals(statementType) || "MERGE".equals(statementType);
-    }
-    
-    private record CommonTableExpressionResolution(String aliasName, String sql, int nextIndex) {
-    }
-}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementTargetResolver.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementTargetResolver.java
deleted file mode 100644
index 5a34c7895b0..00000000000
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLStatementTargetResolver.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * 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.core.tool.handler.execute;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-final class SQLStatementTargetResolver {
-    
-    private static final List<String> SQL_OBJECT_TYPE_KEYWORDS = List.of("TABLE", "VIEW", "INDEX", "SEQUENCE", "DATABASE", "SCHEMA", "TYPE", "FUNCTION", "PROCEDURE", "TRIGGER", "POLICY");
-    
-    private static final List<String> DELETE_MODIFIER_KEYWORDS = List.of("LOW_PRIORITY", "QUICK", "IGNORE");
-    
-    private final SQLStatementScanner scanner;
-    
-    private final SQLStatementStructureResolver structureResolver;
-    
-    private final SQLStatementObjectNameReader objectNameReader;
-    
-    SQLStatementTargetResolver(final SQLStatementScanner scanner, final SQLStatementStructureResolver structureResolver) {
-        this.scanner = scanner;
-        this.structureResolver = structureResolver;
-        objectNameReader = new SQLStatementObjectNameReader(scanner);
-    }
-    
-    Collection<SQLStatementObjectName> resolveAll(final SQLStatementStructure statementStructure) {
-        Set<SQLStatementObjectName> result = new LinkedHashSet<>(16, 1F);
-        collect(statementStructure, new LinkedList<>(), result);
-        return result;
-    }
-    
-    private void collect(final SQLStatementStructure statementStructure, final Collection<String> visitedAliases, final Collection<SQLStatementObjectName> objectNames) {
-        if ("SELECT".equals(statementStructure.statementType())) {
-            collectCommonTableExpressionObjectNames(statementStructure, visitedAliases, objectNames);
-            collectSelectTargetObjectNames(statementStructure, visitedAliases, objectNames);
-            return;
-        }
-        collectDirectTargetObjectNames(statementStructure, visitedAliases, objectNames);
-        collectClauseObjectNames(statementStructure, visitedAliases, objectNames, getClauseKeywords(statementStructure.statementType()));
-        collectCommonTableExpressionObjectNames(statementStructure, visitedAliases, objectNames);
-    }
-    
-    private void collectCommonTableExpressionObjectNames(final SQLStatementStructure statementStructure, final Collection<String> visitedAliases,
-                                                         final Collection<SQLStatementObjectName> objectNames) {
-        for (SQLCommonTableExpression each : statementStructure.commonTableExpressions()) {
-            String normalizedAliasName = scanner.normalizeIdentifierForComparison(each.aliasName());
-            if (visitedAliases.contains(normalizedAliasName)) {
-                continue;
-            }
-            collect(each.statementStructure(), appendVisitedAlias(visitedAliases, normalizedAliasName), objectNames);
-        }
-    }
-    
-    private void collectSelectTargetObjectNames(final SQLStatementStructure statementStructure, final Collection<String> visitedAliases,
-                                                final Collection<SQLStatementObjectName> objectNames) {
-        collectClauseObjectNames(statementStructure, visitedAliases, objectNames, "FROM", "JOIN");
-    }
-    
-    private void collectClauseObjectNames(final SQLStatementStructure statementStructure, final Collection<String> visitedAliases,
-                                          final Collection<SQLStatementObjectName> objectNames, final String... keywords) {
-        List<SQLStatementToken> tokens = scanner.tokenize(statementStructure.mainSql());
-        for (int each : findKeywordIndexes(tokens, keywords)) {
-            collectObjectNamesFromClause(statementStructure, tokens, each, visitedAliases, objectNames);
-        }
-        collectNestedQueryObjectNames(tokens, visitedAliases, objectNames);
-        collectQualifiedFunctionNames(tokens, objectNames);
-    }
-    
-    private void collectObjectNamesFromClause(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final int keywordIndex,
-                                              final Collection<String> visitedAliases, final Collection<SQLStatementObjectName> objectNames) {
-        int objectStartIndex = keywordIndex + 1;
-        if (scanner.isKeyword(tokens.get(keywordIndex), "INHERITS") && objectStartIndex < tokens.size() && "(".equals(tokens.get(objectStartIndex).text())) {
-            objectStartIndex++;
-        }
-        collectObjectNamesFromList(statementStructure, tokens, objectStartIndex, visitedAliases, objectNames, "ONLY", "LATERAL");
-    }
-    
-    private void collectNestedQueryObjectNames(final List<SQLStatementToken> tokens, final Collection<String> visitedAliases,
-                                               final Collection<SQLStatementObjectName> objectNames) {
-        int index = 0;
-        while (index < tokens.size()) {
-            if (!"(".equals(tokens.get(index).text())) {
-                index++;
-                continue;
-            }
-            int closingParenthesisIndex = findClosingParenthesis(tokens, index);
-            if (isNestedQuery(tokens, index + 1, closingParenthesisIndex)) {
-                SQLStatementStructure statementStructure = structureResolver.resolve(reconstructSql(tokens, index + 1, closingParenthesisIndex));
-                collect(statementStructure, visitedAliases, objectNames);
-                index = closingParenthesisIndex + 1;
-                continue;
-            }
-            index++;
-        }
-    }
-    
-    private void collectObjectName(final SQLStatementStructure statementStructure, final SQLStatementObjectName objectName, final Collection<String> visitedAliases,
-                                   final Collection<SQLStatementObjectName> objectNames) {
-        Optional<SQLCommonTableExpression> commonTableExpression = findCommonTableExpression(statementStructure, objectName);
-        if (commonTableExpression.isEmpty()) {
-            addObjectName(objectNames, objectName);
-            return;
-        }
-        SQLCommonTableExpression actualCommonTableExpression = commonTableExpression.get();
-        String normalizedAliasName = scanner.normalizeIdentifierForComparison(actualCommonTableExpression.aliasName());
-        if (!visitedAliases.contains(normalizedAliasName)) {
-            collect(actualCommonTableExpression.statementStructure(), appendVisitedAlias(visitedAliases, normalizedAliasName), objectNames);
-        }
-    }
-    
-    private void collectDirectTargetObjectNames(final SQLStatementStructure statementStructure, final Collection<String> visitedAliases,
-                                                final Collection<SQLStatementObjectName> objectNames) {
-        List<SQLStatementToken> tokens = scanner.tokenize(statementStructure.mainSql());
-        String statementType = statementStructure.statementType();
-        if ("INSERT".equals(statementType) || "MERGE".equals(statementType)) {
-            collectObjectNamesAfterKeyword(statementStructure, tokens, "INTO", visitedAliases, objectNames);
-            return;
-        }
-        if ("UPDATE".equals(statementType)) {
-            collectObjectNamesAfterKeyword(statementStructure, tokens, "UPDATE", visitedAliases, objectNames, "ONLY");
-            return;
-        }
-        if ("DELETE".equals(statementType)) {
-            collectDeleteTargetObjectNames(statementStructure, tokens, visitedAliases, objectNames);
-            return;
-        }
-        if ("CREATE".equals(statementType)) {
-            collectObjectNamesAfterTypeKeyword(statementStructure, tokens, visitedAliases, objectNames, "CONCURRENTLY", "IF", "NOT", "EXISTS");
-            collectCreateSourceObjectNames(statementStructure, tokens, visitedAliases, objectNames);
-            return;
-        }
-        if ("ALTER".equals(statementType)) {
-            collectObjectNamesAfterTypeKeyword(statementStructure, tokens, visitedAliases, objectNames, "IF", "EXISTS", "ONLY");
-            collectAlterDestinationObjectNames(statementStructure, tokens, visitedAliases, objectNames);
-            return;
-        }
-        if ("DROP".equals(statementType)) {
-            collectObjectNamesAfterTypeKeyword(statementStructure, tokens, visitedAliases, objectNames, "CONCURRENTLY", "IF", "EXISTS");
-            collectDropSourceObjectNames(statementStructure, tokens, visitedAliases, objectNames);
-            return;
-        }
-        if ("TRUNCATE".equals(statementType)) {
-            if (!collectObjectNamesAfterKeyword(statementStructure, tokens, "TABLE", visitedAliases, objectNames, "ONLY")) {
-                collectObjectNamesFromList(statementStructure, tokens, 1, visitedAliases, objectNames, "ONLY");
-            }
-            return;
-        }
-        if ("GRANT".equals(statementType) || "REVOKE".equals(statementType)) {
-            collectObjectNamesAfterKeyword(statementStructure, tokens, "ON", visitedAliases, objectNames, "TABLE", "VIEW", "INDEX", "SEQUENCE", "DATABASE", "SCHEMA");
-        }
-    }
-    
-    private void collectDeleteTargetObjectNames(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final Collection<String> visitedAliases,
-                                                final Collection<SQLStatementObjectName> objectNames) {
-        int deleteTargetStartIndex = skipDeleteModifierKeywords(tokens, 1);
-        if (deleteTargetStartIndex < tokens.size() && !scanner.isKeyword(tokens.get(deleteTargetStartIndex), "FROM", "USING")) {
-            collectObjectNamesFromList(statementStructure, tokens, deleteTargetStartIndex, visitedAliases, objectNames);
-        }
-        collectObjectNamesAfterKeyword(statementStructure, tokens, "FROM", visitedAliases, objectNames, "ONLY");
-    }
-    
-    private int skipDeleteModifierKeywords(final List<SQLStatementToken> tokens, final int startIndex) {
-        int result = startIndex;
-        while (result < tokens.size() && DELETE_MODIFIER_KEYWORDS.contains(tokens.get(result).upperText())) {
-            result++;
-        }
-        return result;
-    }
-    
-    private String[] getClauseKeywords(final String statementType) {
-        if ("DELETE".equals(statementType)) {
-            return new String[]{"FROM", "JOIN", "USING"};
-        }
-        if ("CREATE".equals(statementType) || "ALTER".equals(statementType)) {
-            return new String[]{"FROM", "JOIN", "REFERENCES", "INHERITS", "INHERIT"};
-        }
-        return "MERGE".equals(statementType) ? new String[]{"USING", "JOIN"} : new String[]{"FROM", "JOIN"};
-    }
-    
-    private void collectCreateSourceObjectNames(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final Collection<String> visitedAliases,
-                                                final Collection<SQLStatementObjectName> objectNames) {
-        if (isStatementObjectType(tokens, "INDEX", "TRIGGER", "POLICY")) {
-            collectObjectNamesAfterKeyword(statementStructure, tokens, "ON", visitedAliases, objectNames);
-        }
-        if (isStatementObjectType(tokens, "TABLE")) {
-            collectObjectNamesAfterKeyword(statementStructure, tokens, "LIKE", visitedAliases, objectNames);
-        }
-    }
-    
-    private void collectDropSourceObjectNames(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final Collection<String> visitedAliases,
-                                              final Collection<SQLStatementObjectName> objectNames) {
-        if (isStatementObjectType(tokens, "INDEX")) {
-            collectObjectNamesAfterKeyword(statementStructure, tokens, "ON", visitedAliases, objectNames);
-        }
-    }
-    
-    private void collectAlterDestinationObjectNames(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final Collection<String> visitedAliases,
-                                                    final Collection<SQLStatementObjectName> objectNames) {
-        for (int index = 0; index < tokens.size(); index++) {
-            if (isRenameToDestination(tokens, index)) {
-                collectObjectNamesFromList(statementStructure, tokens, index + 2, visitedAliases, objectNames);
-            }
-            if (isSetSchemaDestination(tokens, index)) {
-                collectObjectNamesFromList(statementStructure, tokens, index + 2, visitedAliases, objectNames);
-            }
-        }
-        if (isStatementObjectType(tokens, "TRIGGER", "POLICY")) {
-            collectObjectNamesAfterKeyword(statementStructure, tokens, "ON", visitedAliases, objectNames);
-        }
-    }
-    
-    private boolean isRenameToDestination(final List<SQLStatementToken> tokens, final int index) {
-        return index + 2 < tokens.size() && scanner.isKeyword(tokens.get(index), "RENAME") && scanner.isKeyword(tokens.get(index + 1), "TO");
-    }
-    
-    private boolean isSetSchemaDestination(final List<SQLStatementToken> tokens, final int index) {
-        return index + 2 < tokens.size() && scanner.isKeyword(tokens.get(index), "SET") && scanner.isKeyword(tokens.get(index + 1), "SCHEMA");
-    }
-    
-    private boolean isStatementObjectType(final List<SQLStatementToken> tokens, final String... keywords) {
-        for (SQLStatementToken each : tokens) {
-            if (SQL_OBJECT_TYPE_KEYWORDS.contains(each.upperText())) {
-                return scanner.isKeyword(each, keywords);
-            }
-        }
-        return false;
-    }
-    
-    private Collection<String> appendVisitedAlias(final Collection<String> visitedAliases, final String aliasName) {
-        Collection<String> result = new LinkedList<>(visitedAliases);
-        result.add(aliasName);
-        return result;
-    }
-    
-    private Optional<SQLCommonTableExpression> findCommonTableExpression(final SQLStatementStructure statementStructure, final SQLStatementObjectName objectName) {
-        if (objectName.qualified()) {
-            return Optional.empty();
-        }
-        String normalizedAliasName = scanner.normalizeIdentifierForComparison(objectName.objectName());
-        for (SQLCommonTableExpression each : statementStructure.commonTableExpressions()) {
-            if (scanner.normalizeIdentifierForComparison(each.aliasName()).equals(normalizedAliasName)) {
-                return Optional.of(each);
-            }
-        }
-        return Optional.empty();
-    }
-    
-    private void collectObjectNamesAfterTypeKeyword(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final Collection<String> visitedAliases,
-                                                    final Collection<SQLStatementObjectName> objectNames, final String... optionalKeywords) {
-        for (int index = 0; index < tokens.size(); index++) {
-            if (SQL_OBJECT_TYPE_KEYWORDS.contains(tokens.get(index).upperText())) {
-                collectObjectNamesFromList(statementStructure, tokens, index + 1, visitedAliases, objectNames, optionalKeywords);
-                return;
-            }
-        }
-    }
-    
-    private boolean collectObjectNamesAfterKeyword(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final String keyword,
-                                                   final Collection<String> visitedAliases, final Collection<SQLStatementObjectName> objectNames, final String... optionalKeywords) {
-        for (int index = 0; index < tokens.size(); index++) {
-            if (scanner.isKeyword(tokens.get(index), keyword)) {
-                collectObjectNamesFromList(statementStructure, tokens, index + 1, visitedAliases, objectNames, optionalKeywords);
-                return true;
-            }
-        }
-        return false;
-    }
-    
-    private void collectObjectNamesFromList(final SQLStatementStructure statementStructure, final List<SQLStatementToken> tokens, final int startIndex,
-                                            final Collection<String> visitedAliases, final Collection<SQLStatementObjectName> objectNames, final String... optionalKeywords) {
-        int currentIndex = startIndex;
-        while (currentIndex < tokens.size()) {
-            SQLStatementObjectName objectName = objectNameReader.readObjectName(tokens, currentIndex, optionalKeywords);
-            if (objectName.objectName().isEmpty()) {
-                return;
-            }
-            collectObjectName(statementStructure, objectName, visitedAliases, objectNames);
-            currentIndex = objectNameReader.skipObjectTail(tokens, objectName.nextIndex());
-            if (currentIndex >= tokens.size() || !",".equals(tokens.get(currentIndex).text())) {
-                return;
-            }
-            currentIndex++;
-        }
-    }
-    
-    private void collectQualifiedFunctionNames(final List<SQLStatementToken> tokens, final Collection<SQLStatementObjectName> objectNames) {
-        int index = 0;
-        while (index < tokens.size()) {
-            SQLStatementObjectName objectName = objectNameReader.readQualifiedName(tokens, index);
-            if (isQualifiedFunctionName(tokens, objectName)) {
-                addObjectName(objectNames, objectName);
-                index = objectName.nextIndex() - 1;
-            }
-            index++;
-        }
-    }
-    
-    private boolean isQualifiedFunctionName(final List<SQLStatementToken> tokens, final SQLStatementObjectName objectName) {
-        return objectName.objectName().contains(".") && !objectName.objectName().endsWith(".*") && objectName.nextIndex() < tokens.size()
-                && "(".equals(tokens.get(objectName.nextIndex()).text());
-    }
-    
-    private void addObjectName(final Collection<SQLStatementObjectName> objectNames, final SQLStatementObjectName objectName) {
-        if (!objectName.objectName().isEmpty()) {
-            objectNames.add(objectName);
-        }
-    }
-    
-    private List<Integer> findKeywordIndexes(final List<SQLStatementToken> tokens, final String... keywords) {
-        List<Integer> result = new ArrayList<>(tokens.size());
-        int parenthesesDepth = 0;
-        for (int index = 0; index < tokens.size(); index++) {
-            if ("(".equals(tokens.get(index).text())) {
-                parenthesesDepth++;
-                continue;
-            }
-            if (")".equals(tokens.get(index).text())) {
-                parenthesesDepth--;
-                continue;
-            }
-            if (0 == parenthesesDepth && scanner.isKeyword(tokens.get(index), keywords)) {
-                result.add(index);
-            }
-        }
-        return result;
-    }
-    
-    private int findClosingParenthesis(final List<SQLStatementToken> tokens, final int startIndex) {
-        int parenthesesDepth = 0;
-        for (int index = startIndex; index < tokens.size(); index++) {
-            if ("(".equals(tokens.get(index).text())) {
-                parenthesesDepth++;
-                continue;
-            }
-            if (")".equals(tokens.get(index).text())) {
-                parenthesesDepth--;
-                if (0 == parenthesesDepth) {
-                    return index;
-                }
-            }
-        }
-        return tokens.size();
-    }
-    
-    private boolean isNestedQuery(final List<SQLStatementToken> tokens, final int startIndex, final int stopIndex) {
-        return startIndex < stopIndex && scanner.isKeyword(tokens.get(startIndex), "SELECT", "WITH");
-    }
-    
-    private String reconstructSql(final List<SQLStatementToken> tokens, final int startIndex, final int stopIndex) {
-        StringBuilder result = new StringBuilder();
-        for (int index = startIndex; index < stopIndex; index++) {
-            if (!result.isEmpty()) {
-                result.append(' ');
-            }
-            result.append(tokens.get(index).text());
-        }
-        return result.toString();
-    }
-    
-}
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/StatementClassifier.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/StatementClassifier.java
deleted file mode 100644
index e2451732b3d..00000000000
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/StatementClassifier.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.core.tool.handler.execute;
-
-import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
-import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
-import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
-
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Classify one SQL statement into the MCP statement classes.
- */
-public final class StatementClassifier {
-    
-    private final SQLStatementScanner scanner = new SQLStatementScanner();
-    
-    private final SQLStatementSafetyValidator safetyValidator = new SQLStatementSafetyValidator(scanner);
-    
-    private final SQLStatementStructureResolver structureResolver = new SQLStatementStructureResolver(scanner);
-    
-    private final SQLStatementClassResolver statementClassResolver = new SQLStatementClassResolver();
-    
-    private final SQLStatementTargetResolver targetResolver = new SQLStatementTargetResolver(scanner, structureResolver);
-    
-    /**
-     * Classify one SQL statement.
-     *
-     * @param sql SQL text
-     * @return classification result
-     */
-    public ClassificationResult classify(final String sql) {
-        String actualSql = scanner.normalizeSingleStatement(sql);
-        String leadingSql = actualSql.substring(scanner.skipInsignificant(actualSql, 0)).trim();
-        String upperLeadingSql = leadingSql.toUpperCase(Locale.ENGLISH);
-        safetyValidator.checkLeadingStatement(upperLeadingSql, actualSql);
-        if (isSavepointStatement(upperLeadingSql)) {
-            String statementType = extractStatementType(upperLeadingSql);
-            String savepointName = extractSavepointName(leadingSql);
-            validateSavepointName(statementType, savepointName);
-            return new ClassificationResult(SupportedMCPStatement.SAVEPOINT, statementType, actualSql, savepointName, List.of());
-        }
-        if (isTransactionControlStatement(upperLeadingSql)) {
-            return new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, extractStatementType(upperLeadingSql), actualSql, "", List.of());
-        }
-        SQLStatementStructure statementStructure = structureResolver.resolve(actualSql);
-        SupportedMCPStatement statementClass = statementClassResolver.resolve(statementStructure);
-        safetyValidator.checkStructuredStatement(statementClass, statementStructure);
-        return new ClassificationResult(statementClass, statementStructure.statementType(), actualSql, "", targetResolver.resolveAll(statementStructure));
-    }
-    
-    private boolean isTransactionControlStatement(final String upperSql) {
-        return "BEGIN".equals(upperSql)
-                || "START TRANSACTION".equals(upperSql)
-                || "COMMIT".equals(upperSql)
-                || "ROLLBACK".equals(upperSql);
-    }
-    
-    private boolean isSavepointStatement(final String upperSql) {
-        return "SAVEPOINT".equals(upperSql)
-                || upperSql.startsWith("SAVEPOINT ")
-                || "ROLLBACK TO".equals(upperSql)
-                || upperSql.startsWith("ROLLBACK TO ")
-                || "RELEASE SAVEPOINT".equals(upperSql)
-                || upperSql.startsWith("RELEASE SAVEPOINT ");
-    }
-    
-    private String extractStatementType(final String upperSql) {
-        if (upperSql.startsWith("START TRANSACTION")) {
-            return "START TRANSACTION";
-        }
-        if (upperSql.startsWith("ROLLBACK TO SAVEPOINT")) {
-            return "ROLLBACK TO SAVEPOINT";
-        }
-        if (upperSql.startsWith("ROLLBACK TO")) {
-            return "ROLLBACK TO";
-        }
-        if (upperSql.startsWith("RELEASE SAVEPOINT")) {
-            return "RELEASE SAVEPOINT";
-        }
-        return upperSql.split("\\s+")[0];
-    }
-    
-    private String extractSavepointName(final String sql) {
-        String[] tokens = sql.split("\\s+");
-        if ("SAVEPOINT".equalsIgnoreCase(tokens[0]) && 2 == tokens.length) {
-            return tokens[1];
-        }
-        if ("RELEASE".equalsIgnoreCase(tokens[0]) && 3 == tokens.length && "SAVEPOINT".equalsIgnoreCase(tokens[1])) {
-            return tokens[2];
-        }
-        if ("ROLLBACK".equalsIgnoreCase(tokens[0]) && tokens.length >= 3 && "TO".equalsIgnoreCase(tokens[1])) {
-            if (3 == tokens.length && !"SAVEPOINT".equalsIgnoreCase(tokens[2])) {
-                return tokens[2];
-            }
-            if (4 == tokens.length && "SAVEPOINT".equalsIgnoreCase(tokens[2])) {
-                return tokens[3];
-            }
-        }
-        return "";
-    }
-    
-    private void validateSavepointName(final String statementType, final String savepointName) {
-        if (!savepointName.isEmpty()) {
-            return;
-        }
-        ShardingSpherePreconditions.checkState(!isSavepointStatementType(statementType), () -> new MCPInvalidRequestException("Savepoint name is required."));
-    }
-    
-    private boolean isSavepointStatementType(final String statementType) {
-        return "SAVEPOINT".equals(statementType)
-                || "ROLLBACK TO".equals(statementType)
-                || "ROLLBACK TO SAVEPOINT".equals(statementType)
-                || "RELEASE SAVEPOINT".equals(statementType);
-    }
-}
diff --git a/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml b/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml
index 9c11dcc770c..d4adb4faff3 100644
--- a/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml
+++ b/mcp/core/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-core.yaml
@@ -1229,7 +1229,7 @@ tools:
   - name: database_gateway_execute_query
     title: Execute Query SQL
     description: >-
-      Execute exactly one classifier-approved SELECT statement against a ShardingSphere logical database. Use for inspection only;
+      Execute exactly one parser-approved SELECT statement against a ShardingSphere logical database. Use for inspection only;
       DML, DDL, DCL, transaction control, savepoints, and known side-effecting query forms are rejected.
     inputSchema:
       type: object
@@ -1242,7 +1242,7 @@ tools:
           description: "Optional schema or namespace hint for unqualified object names."
         sql:
           type: string
-          description: "One classifier-approved SELECT statement. Do not include EXPLAIN, multiple statements, or mutating SQL."
+          description: "One parser-approved SELECT statement. Do not include EXPLAIN, multiple statements, or mutating SQL."
         max_rows:
           type: integer
           description: "Optional maximum number of rows to return. Omit or pass 0 to use the server default of 100."
@@ -1386,7 +1386,7 @@ tools:
   - name: database_gateway_execute_explain_query
     title: Execute Explain Query SQL
     description: >-
-      Execute exactly one model-generated database-native EXPLAIN SQL for one classifier-approved SELECT statement. Pass the original SELECT as sql
+      Execute exactly one model-generated database-native EXPLAIN SQL for one parser-approved SELECT statement. Pass the original SELECT as sql
       and the generated EXPLAIN as explain_sql. EXPLAIN ANALYZE and EXPLAIN PLAN FOR workflows are rejected.
     inputSchema:
       type: object
@@ -1399,7 +1399,7 @@ tools:
           description: "Optional schema or namespace hint for unqualified object names."
         sql:
           type: string
-          description: "Original classifier-approved SELECT statement to explain. Do not include EXPLAIN, multiple statements, or mutating SQL."
+          description: "Original parser-approved SELECT statement to explain. Do not include EXPLAIN, multiple statements, or mutating SQL."
         explain_sql:
           type: string
           description: "Database-native EXPLAIN SQL generated for sql. It must start with EXPLAIN, include sql without rewriting it, and must not use ANALYZE."
@@ -1548,7 +1548,7 @@ tools:
     title: Preview or Execute Side-Effecting SQL
     description: >-
       Execute or preview exactly one supported SQL statement that may mutate data, metadata, rules, or transaction state.
-      Use execution_mode=preview first when the side-effect scope has not been reviewed. Preview is classification-only,
+      Use execution_mode=preview first when the side-effect scope has not been reviewed. Preview performs database-aware validation and classification,
       not a database dry run. For natural-language rule changes, prefer the matching database_gateway_plan_* workflow tool.
     inputSchema:
       type: object
@@ -1627,7 +1627,7 @@ tools:
         preview_semantics:
           type: string
           description: >-
-            Preview meaning. classification_only means the server classified SQL and side-effect scope without parsing, rule validation,
+            Preview meaning. classification_only means the server parsed and classified SQL and side-effect scope without rule validation,
             algorithm initialization, affected-row estimation, runtime execution, or database dry-run guarantees.
           enum:
             - classification_only
@@ -1636,7 +1636,7 @@ tools:
           description: "Execution or preview status returned by the server."
         normalized_sql:
           type: string
-          description: "Classifier-normalized SQL returned by preview or executed responses."
+          description: "Normalized single SQL statement accepted by MCP for the target database."
         affected_rows:
           type: integer
           description: "Affected row count when the database reports one."
@@ -1745,7 +1745,7 @@ tools:
           would_execute: false
           affected_rows_estimated: false
           review_guidance: >-
-            Review normalized_sql and side_effect_scope before execution. This preview is classification-only; it does not guarantee parsing,
+            Review normalized_sql and side_effect_scope before execution. This preview performs database-aware validation and classification; it does not guarantee
             rule validation, algorithm initialization, affected rows, or runtime success.
           review_summary: Previewed UPDATE statement with side-effect scope physical-data. It has not been executed.
           suggested_arguments:
@@ -1768,32 +1768,22 @@ tools:
                 - 1
               reason: Execute only after reviewing normalized_sql and side_effect_scope; preview did not validate runtime executability.
         - response_mode: executed
-          summary: Executed side-effecting SQL (statement type SELECT) and returned 1 row(s). Returned rows were truncated; do not replay the statement automatically.
-          result_kind: result_set
+          summary: Executed UPDATE statement and affected 1 row(s).
+          result_kind: update_count
           execution_mode: execute
           statement_class: dml
-          statement_type: SELECT
+          statement_type: UPDATE
           status: OK
-          normalized_sql: "WITH updated AS (UPDATE orders SET status = 'PAID' WHERE order_id = 1 RETURNING order_id) SELECT order_id FROM updated"
-          columns:
-            - columnName: order_id
-              logicalType: BIGINT
-              nativeType: BIGINT
-              nullable: false
-          rows:
-            - - 1
-          row_objects:
-            - order_id: 1
-          row_object_status: available
-          returned_row_count: 1
+          normalized_sql: "UPDATE orders SET status = 'PAID' WHERE order_id = 1"
+          affected_rows: 1
           applied_max_rows: 1
           applied_timeout_ms: 0
-          truncated: true
+          truncated: false
           next_actions:
             - order: 1
               type: terminal
               title: Stop
-              reason: The side-effecting statement already executed and returned truncated rows; do not replay it automatically. Use a separate read-only query if more data is needed.
+              reason: The side-effecting statement already executed; do not replay it automatically.
     annotations:
       title: Preview or Execute Side-Effecting SQL
       readOnlyHint: false
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java
index c155f42b5d9..d18e429094a 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/performance/MCPPerformanceBudgetSmokeTest.java
@@ -24,7 +24,6 @@ import org.apache.shardingsphere.mcp.core.context.MCPRuntimeContext;
 import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory;
 import org.apache.shardingsphere.mcp.core.resource.ResourceTestDataFactory.RequestScopeFixture;
 import org.apache.shardingsphere.mcp.core.resource.handler.capability.ServerCapabilitiesHandler;
-import org.apache.shardingsphere.mcp.core.tool.handler.execute.StatementClassifier;
 import org.apache.shardingsphere.mcp.core.tool.handler.metadata.SearchMetadataToolHandler;
 import org.apache.shardingsphere.mcp.core.workflow.InMemoryWorkflowSessionStore;
 import org.apache.shardingsphere.mcp.support.completion.MCPCompletionRequest;
@@ -62,8 +61,6 @@ class MCPPerformanceBudgetSmokeTest {
     
     private static final long COMPLETION_BUDGET_MILLIS = 5000L;
     
-    private static final long SQL_CLASSIFIER_BUDGET_MILLIS = 5000L;
-    
     private static final int DESCRIPTOR_ITERATIONS = 100;
     
     private static final int REQUEST_SCOPE_ITERATIONS = 200;
@@ -74,8 +71,6 @@ class MCPPerformanceBudgetSmokeTest {
     
     private static final int COMPLETION_ITERATIONS = 1000;
     
-    private static final int SQL_CLASSIFIER_ITERATIONS = 1000;
-    
     @Test
     void assertDescriptorGenerationBudget() {
         MCPRuntimeContext runtimeContext = ResourceTestDataFactory.createRuntimeContext();
@@ -150,18 +145,6 @@ class MCPPerformanceBudgetSmokeTest {
         assertWithinBudget("workflow plan id completion", elapsedMillis, COMPLETION_BUDGET_MILLIS);
     }
     
-    @Test
-    void assertSQLClassifierBudget() {
-        StatementClassifier classifier = new StatementClassifier();
-        assertDoesNotThrow(() -> classifier.classify("SELECT * FROM orders WHERE order_id = 1"));
-        long elapsedMillis = measureElapsedMillis(() -> {
-            for (int i = 0; i < SQL_CLASSIFIER_ITERATIONS; i++) {
-                classifier.classify("SELECT * FROM orders WHERE order_id = 1");
-            }
-        });
-        assertWithinBudget("SQL classifier", elapsedMillis, SQL_CLASSIFIER_BUDGET_MILLIS);
-    }
-    
     private WorkflowContextSnapshot createWorkflowSnapshot(final String planId) {
         WorkflowRequest request = new WorkflowRequest();
         request.setDatabase("logic_db");
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java
index 61ffa19f762..5f6fe3a52bb 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverterTest.java
@@ -267,7 +267,7 @@ class MCPErrorConverterTest {
     @Test
     void assertConvertUnsupportedMessageWithoutRecovery() {
         Map<String, Object> actual = MCPErrorConverter.convert(new MCPUnsupportedException(
-                "database_gateway_execute_query only supports classifier-approved QUERY statements.")).toPayload();
+                "database_gateway_execute_query only supports parser-approved QUERY statements.")).toPayload();
         assertFalse(actual.containsKey("recovery"));
     }
     
@@ -281,7 +281,7 @@ class MCPErrorConverterTest {
         when(classificationResult.getSavepointName()).thenReturn(Optional.empty());
         Map<String, Object> suggestedArguments = Map.of("database", "logic_db", "schema", "public", "sql", "UPDATE orders SET status = 'PAID'", "execution_mode", "preview");
         Map<String, Object> actual = MCPErrorConverter.convert(new SQLToolMismatchException(
-                "database_gateway_execute_query only supports classifier-approved QUERY statements. "
+                "database_gateway_execute_query only supports parser-approved QUERY statements. "
                         + "Use database_gateway_execute_explain_query for EXPLAIN diagnostics or database_gateway_execute_update for side-effecting SQL.",
                 "database_gateway_execute_query", "database_gateway_execute_update", classificationResult, suggestedArguments)).toPayload();
         Map<?, ?> actualRecovery = (Map<?, ?>) actual.get("recovery");
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
index 03c15946c78..2f0d3a2d322 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/resource/handler/capability/ServerCapabilitiesHandlerTest.java
@@ -304,26 +304,23 @@ class ServerCapabilitiesHandlerTest {
         assertTrue(executeUpdateOutputProperties.containsKey("rows"));
         assertFalse(executeUpdateOutputProperties.containsKey("approval_summary"));
         assertThat(((Map<?, ?>) executeUpdateOutputProperties.get("normalized_sql")).get("description"),
-                is("Classifier-normalized SQL returned by preview or executed responses."));
-        Map<?, ?> executedResultSetExample = findByKey((List<?>) ((Map<?, ?>) executeUpdateTool.get("outputSchema")).get("examples"), "response_mode", "executed");
-        assertThat(executedResultSetExample.get("result_kind"), is("result_set"));
-        assertThat(executedResultSetExample.get("statement_class"), is("dml"));
-        assertThat(executedResultSetExample.get("statement_type"), is("SELECT"));
-        assertThat(executedResultSetExample.get("summary"),
-                is("Executed side-effecting SQL (statement type SELECT) and returned 1 row(s). Returned rows were truncated; do not replay the statement automatically."));
-        assertTrue(executedResultSetExample.containsKey("columns"));
-        assertTrue(executedResultSetExample.containsKey("rows"));
-        assertTrue((Boolean) executedResultSetExample.get("truncated"));
-        Map<?, ?> executedResultSetNextAction = (Map<?, ?>) ((List<?>) executedResultSetExample.get("next_actions")).get(0);
-        assertThat(executedResultSetNextAction.get("type"), is("terminal"));
-        assertThat(executedResultSetNextAction.get("reason"), is(
-                "The side-effecting statement already executed and returned truncated rows; do not replay it automatically. Use a separate read-only query if more data is needed."));
+                is("Normalized single SQL statement accepted by MCP for the target database."));
+        Map<?, ?> executedExample = findByKey((List<?>) ((Map<?, ?>) executeUpdateTool.get("outputSchema")).get("examples"), "response_mode", "executed");
+        assertThat(executedExample.get("result_kind"), is("update_count"));
+        assertThat(executedExample.get("statement_class"), is("dml"));
+        assertThat(executedExample.get("statement_type"), is("UPDATE"));
+        assertThat(executedExample.get("summary"), is("Executed UPDATE statement and affected 1 row(s)."));
+        assertThat(executedExample.get("affected_rows"), is(1));
+        assertFalse((Boolean) executedExample.get("truncated"));
+        Map<?, ?> executedNextAction = (Map<?, ?>) ((List<?>) executedExample.get("next_actions")).get(0);
+        assertThat(executedNextAction.get("type"), is("terminal"));
+        assertThat(executedNextAction.get("reason"), is("The side-effecting statement already executed; do not replay it automatically."));
         assertThat(executeUpdateTool.get("title"), is("Preview or Execute Side-Effecting SQL"));
-        assertTrue(((String) executeUpdateTool.get("description")).contains("Preview is classification-only, not a database dry run."));
+        assertTrue(((String) executeUpdateTool.get("description")).contains("Preview performs database-aware validation and classification, not a database dry run."));
         assertThat(findInputSchema(executeUpdateTool, "execution_mode").get("description"),
                 is("Required safety gate. Use preview to classify side effects without execution; preview is not a database dry run. Use execute only after reviewing the preview."));
         assertThat(((Map<?, ?>) executeUpdateOutputProperties.get("preview_semantics")).get("description"), is(
-                "Preview meaning. classification_only means the server classified SQL and side-effect scope without parsing, rule validation, "
+                "Preview meaning. classification_only means the server parsed and classified SQL and side-effect scope without rule validation, "
                         + "algorithm initialization, affected-row estimation, runtime execution, or database dry-run guarantees."));
         assertThat(getInputFieldNames(executeUpdateTool), is(List.of("database", "schema", "sql", "execution_mode", "max_rows", "timeout_ms")));
         Map<?, ?> applyWorkflowTool = findTool(capabilities, "database_gateway_apply_workflow");
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandlerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandlerTest.java
index 31b2ac275ab..15288bbb25c 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandlerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandlerTest.java
@@ -23,7 +23,9 @@ import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestExc
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnsupportedException;
 import org.apache.shardingsphere.mcp.api.protocol.payload.MCPSuccessPayload;
 import org.apache.shardingsphere.mcp.support.database.MCPDatabaseRequestContext;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureCapabilityFacade;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureExecutionFacade;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPMetadataQueryFacade;
 import org.apache.shardingsphere.mcp.support.database.tool.request.SQLExecutionRequest;
@@ -33,6 +35,7 @@ import org.mockito.ArgumentCaptor;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -53,6 +56,7 @@ class ExecuteQueryToolHandlerTest {
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getSessionId()).thenReturn("session-1");
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext);
         MCPSuccessPayload actual = new ExecuteQueryToolHandler().handle(databaseContext, Map.of("database", "logic_db", "schema", "public", "sql", "select * from orders"));
         assertThat(actual.toPayload().get("statement_class"), is("query"));
         ArgumentCaptor<SQLExecutionRequest> requestCaptor = ArgumentCaptor.forClass(SQLExecutionRequest.class);
@@ -75,6 +79,7 @@ class ExecuteQueryToolHandlerTest {
         when(databaseContext.getSessionId()).thenReturn("session-1");
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
         when(databaseContext.getMetadataQueryFacade()).thenReturn(metadataQueryFacade);
+        mockDatabaseCapability(databaseContext);
         new ExecuteQueryToolHandler().handle(databaseContext, Map.of("database", "logic_db", "sql", "select * from orders"));
         ArgumentCaptor<SQLExecutionRequest> requestCaptor = ArgumentCaptor.forClass(SQLExecutionRequest.class);
         verify(executionFacade).execute(requestCaptor.capture());
@@ -84,11 +89,11 @@ class ExecuteQueryToolHandlerTest {
     @Test
     void assertRejectExplain() {
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
+        mockDatabaseCapability(databaseContext);
         MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class,
                 () -> new ExecuteQueryToolHandler().handle(databaseContext,
                         Map.of("database", "logic_db", "schema", "public", "sql", "EXPLAIN SELECT * FROM orders")));
         assertThat(actual.getMessage(), is("Statement is not supported by the MCP contract."));
-        verifyNoInteractions(databaseContext);
     }
     
     @Test
@@ -96,6 +101,7 @@ class ExecuteQueryToolHandlerTest {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext);
         MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class,
                 () -> new ExecuteQueryToolHandler().handle(databaseContext, Map.of("database", "logic_db", "sql", "SELECT * FROM orders FOR UPDATE")));
         assertThat(actual.getMessage(), is("Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."));
@@ -107,10 +113,11 @@ class ExecuteQueryToolHandlerTest {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext);
         MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class,
                 () -> new ExecuteQueryToolHandler().handle(databaseContext, Map.of("database", "logic_db", "sql", "update orders set status = 'PAID'")));
         assertThat(actual.getMessage(),
-                is("database_gateway_execute_query only supports classifier-approved QUERY statements. "
+                is("database_gateway_execute_query only supports parser-approved QUERY statements. "
                         + "Use database_gateway_execute_explain_query for EXPLAIN diagnostics or database_gateway_execute_update for side-effecting SQL."));
         verifyNoInteractions(executionFacade);
     }
@@ -120,6 +127,7 @@ class ExecuteQueryToolHandlerTest {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext);
         MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class,
                 () -> new ExecuteQueryToolHandler().handle(databaseContext,
                         Map.of("database", "logic_db", "sql", "select * from orders", "max_rows", 5001)));
@@ -137,6 +145,7 @@ class ExecuteQueryToolHandlerTest {
         when(databaseContext.getSessionId()).thenReturn("session-1");
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
         when(databaseContext.getMetadataQueryFacade()).thenReturn(metadataQueryFacade);
+        mockDatabaseCapability(databaseContext);
         new ExecuteQueryToolHandler().handle(databaseContext, Map.of("database", "logic_db", "sql", "select * from orders", "max_rows", 0));
         ArgumentCaptor<SQLExecutionRequest> requestCaptor = ArgumentCaptor.forClass(SQLExecutionRequest.class);
         verify(executionFacade).execute(requestCaptor.capture());
@@ -146,4 +155,12 @@ class ExecuteQueryToolHandlerTest {
     private SQLExecutionResult createQueryResult() {
         return SQLExecutionResult.resultSet(SupportedMCPStatement.QUERY, "SELECT", List.of(), List.of(), false, 100, 0, "SELECT * FROM orders");
     }
+    
+    private void mockDatabaseCapability(final MCPDatabaseRequestContext databaseContext) {
+        MCPFeatureCapabilityFacade capabilityFacade = mock(MCPFeatureCapabilityFacade.class);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        when(capability.getDatabaseType()).thenReturn("MySQL");
+        when(capabilityFacade.provide("logic_db")).thenReturn(Optional.of(capability));
+        when(databaseContext.getCapabilityFacade()).thenReturn(capabilityFacade);
+    }
 }
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java
index 9f51c2934a8..9e20f95ac06 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteUpdateToolHandlerTest.java
@@ -22,7 +22,9 @@ import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnsupportedExcept
 import org.apache.shardingsphere.mcp.api.protocol.payload.MCPSuccessPayload;
 import org.apache.shardingsphere.mcp.core.protocol.exception.MCPInvalidToolArgumentException;
 import org.apache.shardingsphere.mcp.support.database.MCPDatabaseRequestContext;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureCapabilityFacade;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureExecutionFacade;
 import org.apache.shardingsphere.mcp.support.database.tool.request.SQLExecutionRequest;
 import org.apache.shardingsphere.mcp.support.database.tool.result.SQLExecutionResult;
@@ -31,6 +33,7 @@ import org.mockito.ArgumentCaptor;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -51,6 +54,7 @@ class ExecuteUpdateToolHandlerTest {
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getSessionId()).thenReturn("session-1");
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext, "logic_db");
         MCPSuccessPayload actual = new ExecuteUpdateToolHandler().handle(databaseContext,
                 Map.of("database", "logic_db", "schema", "public", "sql", "update orders set status = 'PAID'", "execution_mode", "execute"));
         assertThat(actual.toPayload().get("response_mode"), is("executed"));
@@ -72,6 +76,7 @@ class ExecuteUpdateToolHandlerTest {
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getSessionId()).thenReturn("session-1");
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext, "logic_db");
         MCPSuccessPayload actual = new ExecuteUpdateToolHandler().handle(databaseContext,
                 Map.of("database", "logic_db", "schema", "public", "sql", "update orders set status = 'PAID'", "execution_mode", "execute"));
         assertThat(actual.toPayload().get("execution_mode"), is("execute"));
@@ -83,6 +88,7 @@ class ExecuteUpdateToolHandlerTest {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext, "logic_db");
         MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class,
                 () -> new ExecuteUpdateToolHandler().handle(databaseContext, Map.of("database", "logic_db", "sql", "update orders set status = 'PAID'")));
         assertThat(actual.getMessage(), is("database_gateway_execute_update execution_mode is required."));
@@ -94,6 +100,7 @@ class ExecuteUpdateToolHandlerTest {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext, "logic_db");
         MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class,
                 () -> new ExecuteUpdateToolHandler().handle(databaseContext,
                         Map.of("database", "logic_db", "sql", "select * from orders", "execution_mode", "execute")));
@@ -106,6 +113,7 @@ class ExecuteUpdateToolHandlerTest {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext, "logic_db");
         MCPSuccessPayload actual = new ExecuteUpdateToolHandler().handle(databaseContext,
                 Map.of("database", "logic_db", "schema", "public", "sql", "update orders set status = 'PAID'", "execution_mode", "preview"));
         assertThat(actual.toPayload().get("response_mode"), is("preview"));
@@ -120,7 +128,7 @@ class ExecuteUpdateToolHandlerTest {
         assertThat(actual.toPayload().get("review_summary"), is("Previewed UPDATE statement with side-effect scope physical-data. It has not been executed."));
         assertThat(actual.toPayload().get("review_guidance"),
                 is("Review normalized_sql and side_effect_scope before execution. "
-                        + "This preview is classification-only; it does not guarantee parsing, rule validation, algorithm initialization, affected rows, or runtime success."));
+                        + "This preview performs database-aware validation and classification; it does not guarantee rule validation, algorithm initialization, affected rows, or runtime success."));
         assertThat(((Map<?, ?>) actual.toPayload().get("suggested_arguments")).get("execution_mode"), is("execute"));
         assertThat(((Map<?, ?>) actual.toPayload().get("argument_provenance")).get("sql"), is("server_generated"));
         assertThat(((Map<?, ?>) actual.toPayload().get("argument_provenance")).get("execution_mode"), is("server_defaulted"));
@@ -149,6 +157,7 @@ class ExecuteUpdateToolHandlerTest {
         MCPFeatureExecutionFacade executionFacade = mock(MCPFeatureExecutionFacade.class);
         MCPDatabaseRequestContext databaseContext = mock(MCPDatabaseRequestContext.class);
         when(databaseContext.getExecutionFacade()).thenReturn(executionFacade);
+        mockDatabaseCapability(databaseContext, "sharding_db");
         MCPSuccessPayload actual = new ExecuteUpdateToolHandler().handle(databaseContext, Map.of("database", "sharding_db", "sql",
                 "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'), KEY_GENERATE_STRATEGY(COLUMN=id, TYPE(NAME='snowflake')))",
                 "execution_mode", "preview"));
@@ -158,7 +167,7 @@ class ExecuteUpdateToolHandlerTest {
         assertThat(actual.toPayload().get("summary"), is("Previewed CREATE statement with side-effect scope rule-metadata. It has not been executed."));
         assertThat(actual.toPayload().get("review_guidance"),
                 is("Review normalized_sql and side_effect_scope before execution. "
-                        + "This preview is classification-only; it does not guarantee parsing, rule validation, algorithm initialization, affected rows, or runtime success."
+                        + "This preview performs database-aware validation and classification; it does not guarantee rule validation, algorithm initialization, affected rows, or runtime success."
                         + " For natural-language rule changes, prefer the matching database_gateway_plan_* workflow tool before raw execution."));
         verifyNoInteractions(executionFacade);
     }
@@ -193,4 +202,12 @@ class ExecuteUpdateToolHandlerTest {
         return SQLExecutionResult.updateCount(SupportedMCPStatement.DML, "UPDATE", 1, 100, 0, "UPDATE orders SET status = 'PAID'");
     }
     
+    private void mockDatabaseCapability(final MCPDatabaseRequestContext databaseContext, final String database) {
+        MCPFeatureCapabilityFacade capabilityFacade = mock(MCPFeatureCapabilityFacade.class);
+        MCPDatabaseCapability capability = mock(MCPDatabaseCapability.class);
+        when(capability.getDatabaseType()).thenReturn("MySQL");
+        when(capabilityFacade.provide(database)).thenReturn(Optional.of(capability));
+        when(databaseContext.getCapabilityFacade()).thenReturn(capabilityFacade);
+    }
+    
 }
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidatorTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidatorTest.java
index e7c217ab0e6..fa6f0b5e5f2 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidatorTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExplainSQLCandidateValidatorTest.java
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.mcp.core.tool.handler.execute;
 
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
 import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -30,14 +31,18 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 class ExplainSQLCandidateValidatorTest {
     
-    private final ExplainSQLCandidateValidator validator = new ExplainSQLCandidateValidator();
+    private final MCPDatabaseCapability databaseCapability = createCapability();
+    
+    private final ExplainSQLCandidateValidator validator = new ExplainSQLCandidateValidator(new MCPStatementAnalyzer());
     
     @Test
     void assertValidate() {
-        ClassificationResult actual = validator.validate("SELECT * FROM logic_db.foo_orders", "EXPLAIN FORMAT=JSON SELECT * FROM logic_db.foo_orders");
+        ClassificationResult actual = validator.validate("SELECT * FROM logic_db.foo_orders", "EXPLAIN FORMAT=JSON SELECT * FROM logic_db.foo_orders", databaseCapability);
         assertThat(actual.getStatementClass(), is(SupportedMCPStatement.EXPLAIN));
         assertThat(actual.getStatementType(), is("EXPLAIN"));
         assertThat(actual.getNormalizedSql(), is("EXPLAIN FORMAT=JSON SELECT * FROM logic_db.foo_orders"));
@@ -47,27 +52,27 @@ class ExplainSQLCandidateValidatorTest {
     @Test
     void assertValidateWithSignificantWhitespace() {
         ClassificationResult actual = validator.validate("SELECT * FROM foo_orders WHERE status = 'READY  TO SHIP'",
-                "EXPLAIN QUERY TREE SELECT * FROM foo_orders WHERE status = 'READY  TO SHIP'");
+                "EXPLAIN QUERY TREE SELECT * FROM foo_orders WHERE status = 'READY  TO SHIP'", databaseCapability);
         assertThat(actual.getNormalizedSql(), is("EXPLAIN QUERY TREE SELECT * FROM foo_orders WHERE status = 'READY  TO SHIP'"));
     }
     
     @Test
     void assertValidateHiveCBO() {
-        ClassificationResult actual = validator.validate("SELECT * FROM foo_orders", "EXPLAIN CBO SELECT * FROM foo_orders");
+        ClassificationResult actual = validator.validate("SELECT * FROM foo_orders", "EXPLAIN CBO SELECT * FROM foo_orders", databaseCapability);
         assertThat(actual.getNormalizedSql(), is("EXPLAIN CBO SELECT * FROM foo_orders"));
     }
     
     @Test
     void assertValidateWithNonExecutableComments() {
         ClassificationResult actual = validator.validate("SELECT '/*!80018 ANALYZE */' FROM foo_orders",
-                "EXPLAIN /* plan only */ SELECT '/*!80018 ANALYZE */' FROM foo_orders");
+                "EXPLAIN /* plan only */ SELECT '/*!80018 ANALYZE */' FROM foo_orders", databaseCapability);
         assertThat(actual.getNormalizedSql(), is("EXPLAIN /* plan only */ SELECT '/*!80018 ANALYZE */' FROM foo_orders"));
     }
     
     @ParameterizedTest(name = "{0}")
     @MethodSource("assertValidateWithInvalidCandidateCases")
     void assertValidateWithInvalidCandidate(final String name, final String sql, final String explainSql, final String expectedMessage) {
-        MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> validator.validate(sql, explainSql));
+        MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> validator.validate(sql, explainSql, databaseCapability));
         assertThat(actual.getMessage(), is(expectedMessage));
     }
     
@@ -104,4 +109,10 @@ class ExplainSQLCandidateValidatorTest {
                 Arguments.of("output redirection", "SELECT * FROM foo_orders", "EXPLAIN INTO OUTFILE SELECT * FROM foo_orders",
                         "EXPLAIN output redirection is not supported by the MCP explain query tool."));
     }
+    
+    private MCPDatabaseCapability createCapability() {
+        MCPDatabaseCapability result = mock(MCPDatabaseCapability.class);
+        when(result.getDatabaseType()).thenReturn("MySQL");
+        return result;
+    }
 }
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java
index af5a33fa906..c0f8eab8869 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcStatementExecutorTest.java
@@ -100,7 +100,7 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Collections.emptyMap(), transactionResourceManager);
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "UPDATE orders SET status = 'DONE'", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of()),
+                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of(), false),
                 createDatabaseCapability(SchemaExecutionSemantics.BEST_EFFORT));
         assertThat(actual.getAffectedRows(), is(2));
         verify(connection).setSchema("public");
@@ -123,7 +123,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "SELECT status FROM orders", 10, 1000, true),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
         assertThat(actual.getRows().size(), is(1));
         verify(connection).setReadOnly(true);
         verify(connection).setAutoCommit(false);
@@ -141,7 +142,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Collections.emptyMap(), transactionResourceManager);
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "SELECT status FROM orders", 10, 1000, true),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
         assertThat(actual.getRows().size(), is(1));
         verify(connection, never()).setReadOnly(true);
         verify(connection, never()).rollback();
@@ -164,7 +166,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", " ", "SELECT status FROM orders", 0, 0),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
         assertThat(actual.getRows().size(), is(2));
         assertFalse(actual.isTruncated());
         verify(connection, never()).setSchema(anyString());
@@ -186,7 +189,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "SELECT status FROM orders", Integer.MAX_VALUE, 1000),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
         assertThat(actual.getRows().size(), is(0));
         assertFalse(actual.isTruncated());
         verify(statement).setMaxRows(Integer.MAX_VALUE);
@@ -207,7 +211,7 @@ class MCPJdbcStatementExecutorTest {
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "WITH updated_orders AS (UPDATE orders SET status = 'DONE' RETURNING *) SELECT * FROM updated_orders", 10, 1000),
                 new ClassificationResult(SupportedMCPStatement.DML, "SELECT",
-                        "WITH updated_orders AS (UPDATE orders SET status = 'DONE' RETURNING *) SELECT * FROM updated_orders", "", List.of()),
+                        "WITH updated_orders AS (UPDATE orders SET status = 'DONE' RETURNING *) SELECT * FROM updated_orders", "", List.of(), false),
                 createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
         assertThat(actual.getResultKind(), is(SQLExecutionResultKind.RESULT_SET));
         assertThat(actual.getStatementClass(), is(SupportedMCPStatement.DML));
@@ -225,7 +229,7 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "logic_db", "UPDATE orders SET status = 'DONE'", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of()),
+                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of(), false),
                 createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
         assertThat(actual.getAffectedRows(), is(1));
         verify(connection, never()).setSchema(anyString());
@@ -242,7 +246,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "UPDATE orders SET status = 'DONE'", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.BEST_EFFORT)));
+                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.BEST_EFFORT)));
         assertThat(actual.getMessage(), is("schema"));
         verify(connection).setSchema("public");
         verify(connection, never()).createStatement();
@@ -259,7 +264,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         QueryDidNotReturnResultSetException actual = assertThrows(QueryDidNotReturnResultSetException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "SELECT status FROM orders", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
         assertThat(actual.getMessage(), is("Query did not return a result set."));
     }
     
@@ -273,7 +279,7 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         StatementClassNotSupportedException actual = assertThrows(StatementClassNotSupportedException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "BEGIN", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "BEGIN", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+                new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "BEGIN", "", List.of(), false), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
         assertThat(actual.getMessage(), is("Statement class is not supported."));
     }
     
@@ -289,7 +295,7 @@ class MCPJdbcStatementExecutorTest {
         when(databaseConfig.openConnection(anyString())).thenReturn(connection);
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("sharding_db", databaseConfig), transactionResourceManager);
         ClassificationResult classificationResult = new ClassificationResult(SupportedMCPStatement.DDL, "CREATE",
-                "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'))", "", List.of());
+                "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'))", "", List.of(), true);
         RuleDistSQLExecutionException actual = assertThrows(RuleDistSQLExecutionException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "sharding_db", "public", "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'))", 10, 1000),
                 classificationResult, createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
@@ -315,7 +321,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         ShardingSphereMCPException actual = assertThrows(expectedExceptionClass, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "SELECT status FROM orders", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
         assertThat(actual.getClass(), is(expectedExceptionClass));
         assertThat(actual.getMessage(), is(expectedMessage));
         assertThat(actual.getCause(), is(sqlException));
@@ -329,7 +336,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Collections.emptyMap(), transactionResourceManager);
         MCPTransactionStateException actual = assertThrows(MCPTransactionStateException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "SELECT status FROM orders", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
         assertThat(actual.getMessage(), is("Transaction already active."));
         assertThat(actual.getCause(), is(cause));
     }
@@ -341,7 +349,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Collections.emptyMap(), transactionResourceManager);
         MCPUnavailableException actual = assertThrows(MCPUnavailableException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "missing_db", "public", "SELECT status FROM orders", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
         assertThat(actual.getMessage(), is("Database `missing_db` is not configured."));
     }
     
@@ -359,7 +368,7 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         MCPQueryFailedException actual = assertThrows(MCPQueryFailedException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1", "logic_db",
                 "public", "UPDATE orders SET status = 'DONE'", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of()),
+                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of(), false),
                 createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
         assertThat(actual.getMessage(), is("statement close failed"));
     }
@@ -375,7 +384,7 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         SQLExecutionResult actual = statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "public", "UPDATE orders SET status = 'DONE'", 10, 1000),
-                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of()),
+                new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of(), false),
                 createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE));
         assertThat(actual.getAffectedRows(), is(1));
         verify(connection).close();
@@ -390,7 +399,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         assertThrows(NullPointerException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "", "SELECT status FROM orders", 0, 0),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
     }
     
     @Test
@@ -404,7 +414,8 @@ class MCPJdbcStatementExecutorTest {
         MCPJdbcStatementExecutor statementExecutor = new MCPJdbcStatementExecutor(Map.of("logic_db", databaseConfig), transactionResourceManager);
         assertThrows(NullPointerException.class, () -> statementExecutor.execute(new SQLExecutionRequest("session-1",
                 "logic_db", "", "SELECT status FROM orders", 0, 0),
-                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of()), createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
+                new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders", "", List.of(), false),
+                createDatabaseCapability(SchemaExecutionSemantics.FIXED_TO_DATABASE)));
     }
     
     private static MCPDatabaseCapability createDatabaseCapability(final SchemaExecutionSemantics schemaExecutionSemantics) {
@@ -451,21 +462,21 @@ class MCPJdbcStatementExecutorTest {
     private static Stream<Arguments> assertExecuteCases() throws SQLException {
         return Stream.of(
                 Arguments.of("query result set", new SQLExecutionRequest("session-1", "logic_db", "public", "SELECT status FROM orders ORDER BY order_id", 1, 1000),
-                        new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders ORDER BY order_id", "", List.of()),
+                        new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT status FROM orders ORDER BY order_id", "", List.of(), false),
                         createStatementConnection(true, 0, createResultSet(createColumns(), List.of(List.of(1, "NEW"), List.of(2, "DONE")))),
                         SQLExecutionResultKind.RESULT_SET, "SELECT", 1, 0, true),
                 Arguments.of("explain result set", new SQLExecutionRequest("session-1", "logic_db", "public", "EXPLAIN SELECT * FROM orders", 10, 1000),
-                        new ClassificationResult(SupportedMCPStatement.EXPLAIN, "EXPLAIN", "EXPLAIN SELECT * FROM orders", "", List.of()),
+                        new ClassificationResult(SupportedMCPStatement.EXPLAIN, "EXPLAIN", "EXPLAIN SELECT * FROM orders", "", List.of(), false),
                         createStatementConnection(true, 0, createResultSet(List.of(new SQLExecutionColumnDefinition("plan", "VARCHAR", "VARCHAR", true)), List.of(List.of("plan")))),
                         SQLExecutionResultKind.RESULT_SET, "EXPLAIN", 1, 0, false),
                 Arguments.of("dml update count", new SQLExecutionRequest("session-1", "logic_db", "public", "UPDATE orders SET status = 'DONE'", 10, 1000),
-                        new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of()), createStatementConnection(false, 3, null),
+                        new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "", List.of(), false), createStatementConnection(false, 3, null),
                         SQLExecutionResultKind.UPDATE_COUNT, "UPDATE", 0, 3, false),
                 Arguments.of("ddl ack", new SQLExecutionRequest("session-1", "logic_db", "public", "CREATE TABLE orders_archive", 10, 1000),
-                        new ClassificationResult(SupportedMCPStatement.DDL, "CREATE", "CREATE TABLE orders_archive", "", List.of()), createStatementConnection(false, 0, null),
+                        new ClassificationResult(SupportedMCPStatement.DDL, "CREATE", "CREATE TABLE orders_archive", "", List.of(), false), createStatementConnection(false, 0, null),
                         SQLExecutionResultKind.STATEMENT_ACK, "CREATE", 0, 0, false),
                 Arguments.of("dcl ack", new SQLExecutionRequest("session-1", "logic_db", "public", "GRANT SELECT ON orders TO app_user", 10, 1000),
-                        new ClassificationResult(SupportedMCPStatement.DCL, "GRANT", "GRANT SELECT ON orders TO app_user", "", List.of()), createStatementConnection(false, 0, null),
+                        new ClassificationResult(SupportedMCPStatement.DCL, "GRANT", "GRANT SELECT ON orders TO app_user", "", List.of(), false), createStatementConnection(false, 0, null),
                         SQLExecutionResultKind.STATEMENT_ACK, "GRANT", 0, 0, false));
     }
     
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java
index c5e5dede040..bb1550abcf3 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutorTest.java
@@ -54,7 +54,8 @@ class MCPJdbcTransactionStatementExecutorTest {
     
     @ParameterizedTest(name = "{0}")
     @MethodSource("assertExecuteCases")
-    void assertExecute(final String name, final String sql, final String expectedStatementType) throws SQLException {
+    void assertExecute(final String name, final String sql, final SupportedMCPStatement statementClass, final String expectedStatementType,
+                       final String savepointName) throws SQLException {
         Connection connection = mock(Connection.class);
         Savepoint savepoint = mock(Savepoint.class);
         RuntimeDatabaseConfiguration runtimeDatabaseConfig = mock(RuntimeDatabaseConfiguration.class);
@@ -63,7 +64,7 @@ class MCPJdbcTransactionStatementExecutorTest {
         sessionManager.createSession("session-1");
         prepareTransactionState(sql, sessionManager, connection, savepoint);
         MCPJdbcTransactionStatementExecutor executor = new MCPJdbcTransactionStatementExecutor(sessionManager);
-        ClassificationResult classificationResult = new StatementClassifier().classify(sql);
+        ClassificationResult classificationResult = new ClassificationResult(statementClass, expectedStatementType, sql, savepointName, List.of(), false);
         SQLExecutionResult actual = executor.execute("session-1", "logic_db", createCapability(), classificationResult);
         assertThat(actual.getResultKind(), is(SQLExecutionResultKind.STATEMENT_ACK));
         assertThat(actual.getStatementClass(), is(classificationResult.getStatementClass()));
@@ -76,13 +77,13 @@ class MCPJdbcTransactionStatementExecutorTest {
     
     static Stream<Arguments> assertExecuteCases() {
         return Stream.of(
-                Arguments.of("begin", "BEGIN", "BEGIN"),
-                Arguments.of("start transaction", "START TRANSACTION", "START TRANSACTION"),
-                Arguments.of("commit", "COMMIT", "COMMIT"),
-                Arguments.of("rollback", "ROLLBACK", "ROLLBACK"),
-                Arguments.of("savepoint", "SAVEPOINT sp_1", "SAVEPOINT"),
-                Arguments.of("rollback to savepoint", "ROLLBACK TO SAVEPOINT sp_1", "ROLLBACK TO SAVEPOINT"),
-                Arguments.of("release savepoint", "RELEASE SAVEPOINT sp_1", "RELEASE SAVEPOINT"));
+                Arguments.of("begin", "BEGIN", SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", ""),
+                Arguments.of("start transaction", "START TRANSACTION", SupportedMCPStatement.TRANSACTION_CONTROL, "START TRANSACTION", ""),
+                Arguments.of("commit", "COMMIT", SupportedMCPStatement.TRANSACTION_CONTROL, "COMMIT", ""),
+                Arguments.of("rollback", "ROLLBACK", SupportedMCPStatement.TRANSACTION_CONTROL, "ROLLBACK", ""),
+                Arguments.of("savepoint", "SAVEPOINT sp_1", SupportedMCPStatement.SAVEPOINT, "SAVEPOINT", "sp_1"),
+                Arguments.of("rollback to savepoint", "ROLLBACK TO SAVEPOINT sp_1", SupportedMCPStatement.SAVEPOINT, "ROLLBACK TO SAVEPOINT", "sp_1"),
+                Arguments.of("release savepoint", "RELEASE SAVEPOINT sp_1", SupportedMCPStatement.SAVEPOINT, "RELEASE SAVEPOINT", "sp_1"));
     }
     
     @Test
@@ -91,7 +92,8 @@ class MCPJdbcTransactionStatementExecutorTest {
         sessionManager.createSession("session-1");
         MCPJdbcTransactionStatementExecutor executor = new MCPJdbcTransactionStatementExecutor(sessionManager);
         MCPUnsupportedException actual = assertThrows(MCPUnsupportedException.class,
-                () -> executor.execute("session-1", "warehouse", createCapabilityWithoutSavepoint(), new StatementClassifier().classify("SAVEPOINT sp_1")));
+                () -> executor.execute("session-1", "warehouse", createCapabilityWithoutSavepoint(),
+                        new ClassificationResult(SupportedMCPStatement.SAVEPOINT, "SAVEPOINT", "SAVEPOINT sp_1", "sp_1", List.of(), false)));
         assertThat(actual.getMessage(), is("Savepoint is not supported."));
     }
     
@@ -101,7 +103,8 @@ class MCPJdbcTransactionStatementExecutorTest {
         sessionManager.createSession("session-1");
         MCPJdbcTransactionStatementExecutor executor = new MCPJdbcTransactionStatementExecutor(sessionManager);
         MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class,
-                () -> executor.execute("session-1", "logic_db", createCapability(), new StatementClassifier().classify("SELECT 1")));
+                () -> executor.execute("session-1", "logic_db", createCapability(),
+                        new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "", List.of(), false)));
         assertThat(actual.getMessage(), is("Statement is not a transaction command."));
     }
     
@@ -110,7 +113,8 @@ class MCPJdbcTransactionStatementExecutorTest {
         MCPSessionManager sessionManager = new MCPSessionManager(Collections.emptyMap());
         MCPJdbcTransactionStatementExecutor executor = new MCPJdbcTransactionStatementExecutor(sessionManager);
         MCPSessionNotExistedException actual = assertThrows(MCPSessionNotExistedException.class,
-                () -> executor.execute("session-1", "logic_db", createCapability(), new StatementClassifier().classify("BEGIN")));
+                () -> executor.execute("session-1", "logic_db", createCapability(),
+                        new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "BEGIN", "", List.of(), false)));
         assertThat(actual.getMessage(), is("Session does not exist."));
     }
     
@@ -120,7 +124,8 @@ class MCPJdbcTransactionStatementExecutorTest {
         sessionManager.createSession("session-1");
         MCPJdbcTransactionStatementExecutor executor = new MCPJdbcTransactionStatementExecutor(sessionManager);
         MCPTransactionStateException actual = assertThrows(MCPTransactionStateException.class,
-                () -> executor.execute("session-1", "logic_db", createCapability(), new StatementClassifier().classify("COMMIT")));
+                () -> executor.execute("session-1", "logic_db", createCapability(),
+                        new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, "COMMIT", "COMMIT", "", List.of(), false)));
         assertThat(actual.getMessage(), is("No active transaction."));
     }
     
@@ -132,7 +137,7 @@ class MCPJdbcTransactionStatementExecutorTest {
         MCPJdbcTransactionStatementExecutor executor = new MCPJdbcTransactionStatementExecutor(sessionManager);
         MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class,
                 () -> executor.execute("session-1", "logic_db", createCapability(),
-                        new ClassificationResult(SupportedMCPStatement.SAVEPOINT, statementType, sql, "", List.of())));
+                        new ClassificationResult(SupportedMCPStatement.SAVEPOINT, statementType, sql, "", List.of(), false)));
         assertThat(actual.getMessage(), is("Savepoint name is required."));
     }
     
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java
index ea36ba20f7d..3bb16e950bb 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPSQLExecutionFacadeTest.java
@@ -84,22 +84,22 @@ class MCPSQLExecutionFacadeTest {
         MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
-        StatementClassifier statementClassifier = mock(StatementClassifier.class);
+        MCPStatementAnalyzer statementAnalyzer = mock(MCPStatementAnalyzer.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
         SQLExecutionRequest request = createExecutionRequest("SELECT 1");
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
-        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "", List.of());
+        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "", List.of(), false);
         SQLExecutionResult expectedResult = mock(SQLExecutionResult.class);
         ArgumentCaptor<Supplier<SQLExecutionResult>> lockedExecution = ArgumentCaptor.forClass(Supplier.class);
         when(coordinator.executeWithSessionLock(eq("session-1"), lockedExecution.capture())).thenReturn(expectedResult);
-        when(statementClassifier.classify("SELECT 1")).thenReturn(classification);
+        when(statementAnalyzer.analyze("SELECT 1", capability)).thenReturn(classification);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
         when(statementExecutor.execute(request, classification, capability)).thenReturn(expectedResult);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, statementClassifier);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, statementAnalyzer);
         assertThat(facade.execute(request), is(expectedResult));
-        verifyNoInteractions(capabilityProvider, transactionExecutor, statementExecutor, statementClassifier, traceFactory);
+        verifyNoInteractions(capabilityProvider, transactionExecutor, statementExecutor, statementAnalyzer, traceFactory);
         assertThat(lockedExecution.getValue().get(), is(expectedResult));
-        verify(statementClassifier).classify("SELECT 1");
+        verify(statementAnalyzer).analyze("SELECT 1", capability);
         verify(capabilityProvider).provide("logic_db");
         verify(statementExecutor).execute(request, classification, capability);
         verify(traceFactory).create("session-1", "logic_db", "SELECT 1", true, "QUERY");
@@ -127,15 +127,15 @@ class MCPSQLExecutionFacadeTest {
     void assertExecuteWithUnsupportedStatement() {
         MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
         MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
-        StatementClassifier statementClassifier = mock(StatementClassifier.class);
+        MCPStatementAnalyzer statementAnalyzer = mock(MCPStatementAnalyzer.class);
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
-        when(statementClassifier.classify(anyString())).thenThrow(new MCPBannedSQLStatementException());
+        when(statementAnalyzer.analyze(anyString(), any())).thenThrow(new MCPBannedSQLStatementException());
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, statementClassifier);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, statementAnalyzer);
         SQLExecutionRequest request = createExecutionRequest("SELECT 1");
         MCPBannedSQLStatementException actual = assertThrows(MCPBannedSQLStatementException.class, () -> facade.execute(request));
         assertThat(actual.getMessage(), is("Statement is banned by the MCP contract."));
@@ -147,18 +147,20 @@ class MCPSQLExecutionFacadeTest {
     void assertExecuteWithUnexpectedClassifierFailure() {
         MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
         MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
-        StatementClassifier statementClassifier = mock(StatementClassifier.class);
+        MCPStatementAnalyzer statementAnalyzer = mock(MCPStatementAnalyzer.class);
+        MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
         mockSessionLock(coordinator);
-        when(statementClassifier.classify(anyString())).thenThrow(new IllegalArgumentException("Malformed SQL."));
+        when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
+        when(statementAnalyzer.analyze(anyString(), any())).thenThrow(new IllegalArgumentException("Malformed SQL."));
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, statementClassifier);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, statementAnalyzer);
         SQLExecutionRequest request = createExecutionRequest("SELECT 1");
         IllegalArgumentException actual = assertThrows(IllegalArgumentException.class, () -> facade.execute(request));
         assertThat(actual.getMessage(), is("Malformed SQL."));
         verify(traceFactory).create("session-1", "logic_db", "SELECT 1", false, "QUERY");
-        verifyNoInteractions(capabilityProvider, transactionExecutor, statementExecutor);
+        verifyNoInteractions(transactionExecutor, statementExecutor);
     }
     
     @Test
@@ -170,8 +172,8 @@ class MCPSQLExecutionFacadeTest {
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
         SQLExecutionRequest request = createExecutionRequest("SELECT 1");
         ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "",
-                List.of(SQLStatementObjectName.fromNormalizedName("orders")));
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+                List.of(SQLStatementObjectName.fromNormalizedName("orders")), false);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createMCPStatementAnalyzer(classification));
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.DML));
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
@@ -187,14 +189,14 @@ class MCPSQLExecutionFacadeTest {
         MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.TRANSACTION_CONTROL));
-        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "BEGIN", "", List.of());
+        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "BEGIN", "", List.of(), false);
         SQLExecutionResult expectedResult = mock(SQLExecutionResult.class);
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
         when(transactionExecutor.execute("session-1", "logic_db", capability, classification)).thenReturn(expectedResult);
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createMCPStatementAnalyzer(classification));
         SQLExecutionRequest request = createExecutionRequest("BEGIN");
         SQLExecutionResult actual = facade.execute(request);
         assertThat(actual, is(expectedResult));
@@ -210,7 +212,7 @@ class MCPSQLExecutionFacadeTest {
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
         ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "",
-                List.of(SQLStatementObjectName.fromNormalizedName("orders")));
+                List.of(SQLStatementObjectName.fromNormalizedName("orders")), false);
         SQLExecutionResult expectedResult = mock(SQLExecutionResult.class);
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
@@ -218,7 +220,7 @@ class MCPSQLExecutionFacadeTest {
         when(statementExecutor.execute(request, classification, capability)).thenReturn(expectedResult);
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createMCPStatementAnalyzer(classification));
         SQLExecutionResult actual = facade.execute(request);
         assertThat(actual, is(expectedResult));
         verify(statementExecutor).execute(request, classification, capability);
@@ -236,8 +238,8 @@ class MCPSQLExecutionFacadeTest {
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
         SQLExecutionRequest request = createExecutionRequest("SELECT * FROM other_db.orders");
         ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT * FROM other_db.orders", "",
-                List.of(SQLStatementObjectName.fromNormalizedName("other_db.orders")));
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+                List.of(SQLStatementObjectName.fromNormalizedName("other_db.orders")), false);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createMCPStatementAnalyzer(classification));
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
         MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> facade.execute(request));
@@ -293,8 +295,8 @@ class MCPSQLExecutionFacadeTest {
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.DML));
         SQLExecutionRequest request = createExecutionRequest("UPDATE other_db.orders SET status = 'DONE'");
         ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.DML, "UPDATE", "UPDATE other_db.orders SET status = 'DONE'", "",
-                List.of(SQLStatementObjectName.fromNormalizedName("other_db.orders")));
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+                List.of(SQLStatementObjectName.fromNormalizedName("other_db.orders")), false);
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createMCPStatementAnalyzer(classification));
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
         MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> facade.execute(request));
@@ -303,26 +305,25 @@ class MCPSQLExecutionFacadeTest {
         verifyNoInteractions(transactionExecutor, statementExecutor);
     }
     
-    @Test
-    void assertExecuteWithQualifiedCurrentDatabase() {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("assertExecuteWithQualifiedCurrentDatabaseCases")
+    void assertExecuteWithQualifiedCurrentDatabase(final String name, final String sql, final SupportedMCPStatement supportedStatement) {
         MCPDatabaseCapabilityProvider capabilityProvider = mock(MCPDatabaseCapabilityProvider.class);
         MCPSessionExecutionCoordinator coordinator = mock(MCPSessionExecutionCoordinator.class);
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
-        MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
-        ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT * FROM logic_db.orders", "",
-                List.of(SQLStatementObjectName.fromNormalizedName("logic_db.orders")));
+        MCPDatabaseCapability capability = createCapability(Set.of(supportedStatement));
         SQLExecutionResult expectedResult = mock(SQLExecutionResult.class);
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
-        SQLExecutionRequest request = createExecutionRequest("SELECT * FROM logic_db.orders");
-        when(statementExecutor.execute(request, classification, capability)).thenReturn(expectedResult);
+        SQLExecutionRequest request = createExecutionRequest(sql);
+        when(statementExecutor.execute(eq(request), any(), eq(capability))).thenReturn(expectedResult);
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory);
         SQLExecutionResult actual = facade.execute(request);
         assertThat(actual, is(expectedResult));
-        verify(statementExecutor).execute(request, classification, capability);
-        verify(traceFactory).create("session-1", "logic_db", "SELECT * FROM logic_db.orders", true, "QUERY");
+        verify(statementExecutor).execute(eq(request), any(), eq(capability));
+        verify(traceFactory).create("session-1", "logic_db", sql, true, supportedStatement.name());
         verifyNoInteractions(transactionExecutor);
     }
     
@@ -364,7 +365,7 @@ class MCPSQLExecutionFacadeTest {
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY), SchemaExecutionSemantics.BEST_EFFORT);
         ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT * FROM other_db.orders", "",
-                List.of(SQLStatementObjectName.fromNormalizedName("other_db.orders")));
+                List.of(SQLStatementObjectName.fromNormalizedName("other_db.orders")), false);
         SQLExecutionResult expectedResult = mock(SQLExecutionResult.class);
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
@@ -372,7 +373,7 @@ class MCPSQLExecutionFacadeTest {
         when(statementExecutor.execute(request, classification, capability)).thenReturn(expectedResult);
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createMCPStatementAnalyzer(classification));
         SQLExecutionResult actual = facade.execute(request);
         assertThat(actual, is(expectedResult));
         verify(statementExecutor).execute(request, classification, capability);
@@ -387,14 +388,14 @@ class MCPSQLExecutionFacadeTest {
         MCPDatabaseCapability capability = createCapability(Set.of(SupportedMCPStatement.QUERY));
         SQLExecutionRequest request = createExecutionRequest("SELECT 1");
         ClassificationResult classification = new ClassificationResult(SupportedMCPStatement.QUERY, "SELECT", "SELECT 1", "",
-                List.of(SQLStatementObjectName.fromNormalizedName("orders")));
+                List.of(SQLStatementObjectName.fromNormalizedName("orders")), false);
         mockSessionLock(coordinator);
         when(capabilityProvider.provide("logic_db")).thenReturn(Optional.of(capability));
         MCPJdbcStatementExecutor statementExecutor = mock(MCPJdbcStatementExecutor.class);
         when(statementExecutor.execute(request, classification, capability)).thenThrow(new MCPInvalidRequestException("bad query"));
         MCPJdbcTransactionStatementExecutor transactionExecutor = mock(MCPJdbcTransactionStatementExecutor.class);
         SQLExecutionTraceFactory traceFactory = mock(SQLExecutionTraceFactory.class);
-        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createStatementClassifier(classification));
+        MCPSQLExecutionFacade facade = createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, createMCPStatementAnalyzer(classification));
         MCPInvalidRequestException actual = assertThrows(MCPInvalidRequestException.class, () -> facade.execute(request));
         assertThat(actual.getMessage(), is("bad query"));
         verify(traceFactory).create("session-1", "logic_db", "SELECT 1", false, "QUERY");
@@ -465,13 +466,13 @@ class MCPSQLExecutionFacadeTest {
     private MCPSQLExecutionFacade createFacade(final MCPDatabaseCapabilityProvider capabilityProvider, final MCPSessionExecutionCoordinator coordinator,
                                                final MCPJdbcTransactionStatementExecutor transactionExecutor, final MCPJdbcStatementExecutor statementExecutor,
                                                final SQLExecutionTraceFactory traceFactory) {
-        return createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, new StatementClassifier());
+        return createFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, traceFactory, new MCPStatementAnalyzer());
     }
     
     private MCPSQLExecutionFacade createFacade(final MCPDatabaseCapabilityProvider capabilityProvider, final MCPSessionExecutionCoordinator coordinator,
                                                final MCPJdbcTransactionStatementExecutor transactionExecutor, final MCPJdbcStatementExecutor statementExecutor,
-                                               final SQLExecutionTraceFactory traceFactory, final StatementClassifier statementClassifier) {
-        return new MCPSQLExecutionFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, statementClassifier, new SQLStatementScanner(), traceFactory);
+                                               final SQLExecutionTraceFactory traceFactory, final MCPStatementAnalyzer statementAnalyzer) {
+        return new MCPSQLExecutionFacade(capabilityProvider, coordinator, transactionExecutor, statementExecutor, statementAnalyzer, new SQLStatementScanner(), traceFactory);
     }
     
     @SuppressWarnings("unchecked")
@@ -488,50 +489,39 @@ class MCPSQLExecutionFacadeTest {
         when(result.getSupportedStatementClasses()).thenReturn(supportedStatementClasses);
         when(result.getSchemaExecutionSemantics()).thenReturn(schemaExecutionSemantics);
         when(result.getIdentifierCasePolicySet()).thenReturn(IdentifierCasePolicyFactory.newInsensitivePolicySet());
+        when(result.getDatabaseType()).thenReturn("MySQL");
         return result;
     }
     
-    private StatementClassifier createStatementClassifier(final ClassificationResult classificationResult) {
-        StatementClassifier result = mock(StatementClassifier.class);
-        when(result.classify(anyString())).thenReturn(classificationResult);
+    private MCPStatementAnalyzer createMCPStatementAnalyzer(final ClassificationResult classificationResult) {
+        MCPStatementAnalyzer result = mock(MCPStatementAnalyzer.class);
+        when(result.analyze(anyString(), any())).thenReturn(classificationResult);
         return result;
     }
     
     private static Stream<Arguments> assertExecuteWithCrossSchemaReferencesDisabledCases() {
         return Stream.of(
                 Arguments.of("query object list", "SELECT * FROM logic_db.orders, other_db.items", "other_db.items", "QUERY"),
-                Arguments.of("query aliased object list", "SELECT * FROM logic_db.orders o, other_db.items i", "other_db.items", "QUERY"),
-                Arguments.of("query partitioned object list", "SELECT * FROM logic_db.orders PARTITION (p0) o, other_db.items i", "other_db.items", "QUERY"),
                 Arguments.of("cte object list", "WITH query_result AS (SELECT * FROM logic_db.orders, other_db.items) SELECT * FROM query_result", "other_db.items", "QUERY"),
-                Arguments.of("qualified object distinct from quoted cte alias", "WITH \"other_db.items\" AS (SELECT 1) SELECT * FROM other_db.items", "other_db.items", "QUERY"),
                 Arguments.of("unused cte reference", "WITH unused_result AS (SELECT * FROM other_db.items) SELECT * FROM logic_db.orders", "other_db.items", "QUERY"),
                 Arguments.of("insert select object list", "INSERT INTO logic_db.orders_archive SELECT * FROM logic_db.orders, other_db.items", "other_db.items", "DML"),
                 Arguments.of("update target object list", "UPDATE logic_db.orders o, other_db.items i SET o.status = 'DONE'", "other_db.items", "DML"),
                 Arguments.of("delete target before from", "DELETE other_db.orders FROM logic_db.orders JOIN other_db.items ON 1 = 1", "other_db.orders", "DML"),
-                Arguments.of("delete using object list", "DELETE FROM logic_db.orders USING other_db.items", "other_db.items", "DML"),
                 Arguments.of("create view object list", "CREATE VIEW logic_db.active_orders AS SELECT * FROM logic_db.orders, other_db.items", "other_db.items", "DDL"),
                 Arguments.of("create table like", "CREATE TABLE logic_db.orders_archive LIKE other_db.orders", "other_db.orders", "DDL"),
                 Arguments.of("create table foreign key reference", "CREATE TABLE logic_db.order_items (order_id INT REFERENCES other_db.orders(id))", "other_db.orders", "DDL"),
-                Arguments.of("create table inherits reference", "CREATE TABLE logic_db.child_orders (LIKE logic_db.orders) INHERITS (other_db.parent_orders)", "other_db.parent_orders", "DDL"),
-                Arguments.of("create index with modifiers", "CREATE INDEX CONCURRENTLY IF NOT EXISTS other_db.orders_idx ON logic_db.orders (status)", "other_db.orders_idx", "DDL"),
-                Arguments.of("create type target", "CREATE TYPE other_db.order_status AS ENUM ('PENDING', 'DONE')", "other_db.order_status", "DDL"),
-                Arguments.of("create function target", "CREATE FUNCTION other_db.refresh_orders() RETURNS INT AS 'SELECT 1'", "other_db.refresh_orders", "DDL"),
-                Arguments.of("create trigger source table", "CREATE TRIGGER refresh_orders AFTER INSERT ON other_db.orders EXECUTE FUNCTION logic_db.refresh_orders()", "other_db.orders", "DDL"),
-                Arguments.of("alter table with modifiers", "ALTER TABLE IF EXISTS other_db.orders ADD COLUMN status VARCHAR(10)", "other_db.orders", "DDL"),
+                Arguments.of("alter table", "ALTER TABLE other_db.orders ADD COLUMN status VARCHAR(10)", "other_db.orders", "DDL"),
                 Arguments.of("alter table foreign key reference",
                         "ALTER TABLE logic_db.order_items ADD CONSTRAINT order_fk FOREIGN KEY (order_id) REFERENCES other_db.orders(id)", "other_db.orders", "DDL"),
-                Arguments.of("alter table inherit reference", "ALTER TABLE logic_db.child_orders INHERIT other_db.parent_orders", "other_db.parent_orders", "DDL"),
-                Arguments.of("alter table rename destination", "ALTER TABLE logic_db.orders RENAME TO other_db.orders_archive", "other_db.orders_archive", "DDL"),
-                Arguments.of("alter table set schema destination", "ALTER TABLE logic_db.orders SET SCHEMA other_db", "other_db", "DDL"),
+                Arguments.of("grant table", "GRANT SELECT ON other_db.orders TO PUBLIC", "other_db.orders", "DCL"),
+                Arguments.of("revoke table", "REVOKE SELECT ON other_db.orders FROM PUBLIC", "other_db.orders", "DCL"),
+                Arguments.of("grant database wildcard", "GRANT SELECT ON other_db.* TO PUBLIC", "other_db.*", "DCL"),
+                Arguments.of("grant global wildcard", "GRANT SELECT ON *.* TO PUBLIC", "*.*", "DCL"),
+                Arguments.of("qualified function", "SELECT other_db.foo_refresh_orders()", "other_db.foo_refresh_orders", "QUERY"),
                 Arguments.of("create database target", "CREATE DATABASE other_db", "other_db", "DDL"),
                 Arguments.of("commented create database target", "/* guard */ CREATE DATABASE other_db", "other_db", "DDL"),
-                Arguments.of("drop schema target", "DROP SCHEMA IF EXISTS other_db", "other_db", "DDL"),
                 Arguments.of("drop table object list", "DROP TABLE IF EXISTS logic_db.orders, other_db.items", "other_db.items", "DDL"),
-                Arguments.of("drop index with modifiers", "DROP INDEX CONCURRENTLY IF EXISTS other_db.orders_idx", "other_db.orders_idx", "DDL"),
-                Arguments.of("truncate table object list", "TRUNCATE TABLE logic_db.orders, other_db.items", "other_db.items", "DDL"),
-                Arguments.of("grant global wildcard", "GRANT SELECT ON *.* TO PUBLIC", "*.*", "DCL"),
-                Arguments.of("grant database target", "GRANT CONNECT ON DATABASE other_db TO PUBLIC", "other_db", "DCL"),
-                Arguments.of("qualified function", "SELECT other_db.refresh_orders()", "other_db.refresh_orders", "QUERY"));
+                Arguments.of("truncate table", "TRUNCATE TABLE other_db.items", "other_db.items", "DDL"));
     }
     
     private static Stream<Arguments> assertExecuteWithNonMatchingCurrentDatabaseIdentifierCases() {
@@ -540,6 +530,13 @@ class MCPSQLExecutionFacadeTest {
                 Arguments.of("quoted identifier exact match", "SELECT * FROM \"Logic_DB\".orders", IdentifierCasePolicyFactory.newInsensitivePolicySet()));
     }
     
+    private static Stream<Arguments> assertExecuteWithQualifiedCurrentDatabaseCases() {
+        return Stream.of(
+                Arguments.of("table", "SELECT * FROM logic_db.orders", SupportedMCPStatement.QUERY),
+                Arguments.of("function", "SELECT logic_db.foo_refresh_orders()", SupportedMCPStatement.QUERY),
+                Arguments.of("grant", "GRANT SELECT ON logic_db.orders TO PUBLIC", SupportedMCPStatement.DCL));
+    }
+    
     private static Stream<Arguments> assertExecuteExplainWithSyntaxFailureCases() {
         return Stream.of(
                 Arguments.of("JDBC syntax exception", new MCPInvalidRequestException("bad explain", new SQLSyntaxErrorException("bad explain"))),
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPStatementAnalyzerTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPStatementAnalyzerTest.java
new file mode 100644
index 00000000000..2092dac90aa
--- /dev/null
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPStatementAnalyzerTest.java
@@ -0,0 +1,310 @@
+/*
+ * 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.core.tool.handler.execute;
+
+import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
+import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnsupportedException;
+import org.apache.shardingsphere.mcp.core.protocol.exception.MCPBannedSQLStatementException;
+import org.apache.shardingsphere.mcp.core.protocol.exception.MCPLockingReadStatementException;
+import org.apache.shardingsphere.mcp.core.protocol.exception.MCPMultipleSQLStatementsException;
+import org.apache.shardingsphere.mcp.core.protocol.exception.MCPUnsupportedSQLStatementException;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapability;
+import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class MCPStatementAnalyzerTest {
+    
+    private final MCPStatementAnalyzer analyzer = new MCPStatementAnalyzer();
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("supportedDatabaseTypes")
+    void assertAnalyzeWithSupportedDatabaseType(final String databaseType, final String sql) {
+        ClassificationResult actual = analyzer.analyze(sql, createCapability(databaseType));
+        assertThat(actual.getStatementClass(), is(SupportedMCPStatement.QUERY));
+        assertThat(actual.getStatementType(), is("SELECT"));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("statementCases")
+    void assertAnalyze(final String name, final String databaseType, final String sql, final SupportedMCPStatement expectedStatementClass,
+                       final String expectedStatementType, final String expectedNormalizedSql, final String expectedTargetObjectName, final String expectedSavepointName) {
+        ClassificationResult actual = analyzer.analyze(sql, createCapability(databaseType));
+        assertThat(actual.getStatementClass(), is(expectedStatementClass));
+        assertThat(actual.getStatementType(), is(expectedStatementType));
+        assertThat(actual.getNormalizedSql(), is(expectedNormalizedSql));
+        assertThat(actual.getTargetObjectName().orElse(""), is(expectedTargetObjectName));
+        assertThat(actual.getSavepointName().orElse(""), is(expectedSavepointName));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("invalidStatementCases")
+    void assertAnalyzeWithInvalidStatement(final String name, final String databaseType, final String sql, final Class<? extends RuntimeException> expectedExceptionClass,
+                                           final String expectedMessage) {
+        RuntimeException actual = assertThrows(expectedExceptionClass, () -> analyzer.analyze(sql, createCapability(databaseType)));
+        assertThat(actual.getMessage(), is(expectedMessage));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("metadataStatementCases")
+    void assertAnalyzeMetadataStatement(final String name, final String sql, final String expectedStatementType) {
+        MetadataIntrospectionSQLStatementException actual = assertThrows(
+                MetadataIntrospectionSQLStatementException.class, () -> analyzer.analyze(sql, createCapability("MySQL")));
+        assertThat(actual.getStatementType(), is(expectedStatementType));
+    }
+    
+    @Test
+    void assertAnalyzeReferencedObjects() {
+        ClassificationResult actual = analyzer.analyze(
+                "SELECT * FROM logic_db.orders JOIN other_db.order_items ON orders.order_id = order_items.order_id", createCapability("MySQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("logic_db.orders", "other_db.order_items"));
+    }
+    
+    @Test
+    void assertAnalyzeCommonTableExpressionReferences() {
+        ClassificationResult actual = analyzer.analyze(
+                "WITH order_result AS (SELECT * FROM other_db.orders) SELECT * FROM order_result", createCapability("PostgreSQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("other_db.orders"));
+    }
+    
+    @Test
+    void assertAnalyzeDMLReferences() {
+        ClassificationResult actual = analyzer.analyze(
+                "UPDATE logic_db.orders SET status = 'DONE' FROM other_db.order_items", createCapability("PostgreSQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("logic_db.orders", "other_db.order_items"));
+    }
+    
+    @Test
+    void assertAnalyzeAlterViewReferences() {
+        ClassificationResult actual = analyzer.analyze(
+                "ALTER VIEW logic_db.active_orders AS SELECT * FROM other_db.orders", createCapability("MySQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("logic_db.active_orders", "other_db.orders"));
+    }
+    
+    @Test
+    void assertAnalyzeSameNamedDMLReferences() {
+        ClassificationResult actual = analyzer.analyze(
+                "UPDATE orders SET status = source.status FROM other_db.orders source", createCapability("PostgreSQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("orders", "other_db.orders"));
+        assertThat(actual.getTargetObjectName().orElse(""), is("orders"));
+    }
+    
+    @Test
+    void assertAnalyzeDCLReference() {
+        ClassificationResult actual = analyzer.analyze("GRANT SELECT ON other_db.orders TO PUBLIC", createCapability("MySQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("other_db.orders"));
+    }
+    
+    @Test
+    void assertAnalyzeGlobalDCLReference() {
+        ClassificationResult actual = analyzer.analyze("GRANT SELECT ON *.* TO PUBLIC", createCapability("MySQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("*.*"));
+    }
+    
+    @Test
+    void assertAnalyzeQualifiedFunctionReference() {
+        ClassificationResult actual = analyzer.analyze("SELECT other_db.foo_refresh_orders()", createCapability("MySQL"));
+        assertThat(actual.getReferencedObjectNames(), contains("other_db.foo_refresh_orders"));
+    }
+    
+    @Test
+    void assertAnalyzeRuleDistSQL() {
+        ClassificationResult actual = analyzer.analyze(
+                "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'))", createCapability("MySQL"));
+        assertTrue(actual.isRuleDistSQL());
+        assertThat(actual.getSideEffectScope(), is("rule-metadata"));
+    }
+    
+    @Test
+    void assertAnalyzePhysicalDDL() {
+        ClassificationResult actual = analyzer.analyze("CREATE TABLE orders(order_id BIGINT)", createCapability("MySQL"));
+        assertFalse(actual.isRuleDistSQL());
+        assertThat(actual.getSideEffectScope(), is("physical-structure"));
+    }
+    
+    private MCPDatabaseCapability createCapability(final String databaseType) {
+        MCPDatabaseCapability result = mock(MCPDatabaseCapability.class);
+        when(result.getDatabaseType()).thenReturn(databaseType);
+        return result;
+    }
+    
+    private static Stream<Arguments> supportedDatabaseTypes() {
+        return Stream.of(
+                Arguments.of("MySQL", "SELECT 1"),
+                Arguments.of("MariaDB", "SELECT 1"),
+                Arguments.of("PostgreSQL", "SELECT 1"),
+                Arguments.of("openGauss", "SELECT 1"),
+                Arguments.of("Oracle", "SELECT 1 FROM DUAL"),
+                Arguments.of("SQLServer", "SELECT 1"),
+                Arguments.of("Firebird", "SELECT 1 FROM RDB$DATABASE"),
+                Arguments.of("ClickHouse", "SELECT 1"),
+                Arguments.of("Hive", "SELECT 1"),
+                Arguments.of("Presto", "SELECT 1"));
+    }
+    
+    private static Stream<Arguments> statementCases() {
+        return Stream.of(
+                Arguments.of("trim trailing semicolon", "MySQL", "  SELECT * FROM orders ;  ", SupportedMCPStatement.QUERY, "SELECT", "SELECT * FROM orders", "orders", ""),
+                Arguments.of("semicolon literal", "MySQL", "SELECT ';' AS literal_value", SupportedMCPStatement.QUERY, "SELECT", "SELECT ';' AS literal_value", "", ""),
+                Arguments.of("with query", "PostgreSQL", "WITH order_result AS (SELECT * FROM orders) SELECT * FROM order_result",
+                        SupportedMCPStatement.QUERY, "SELECT", "WITH order_result AS (SELECT * FROM orders) SELECT * FROM order_result", "orders", ""),
+                Arguments.of("with update", "PostgreSQL", "WITH order_result AS (SELECT * FROM orders) UPDATE order_archive SET status = 'DONE' FROM order_result",
+                        SupportedMCPStatement.DML, "UPDATE", "WITH order_result AS (SELECT * FROM orders) UPDATE order_archive SET status = 'DONE' FROM order_result", "order_archive", ""),
+                Arguments.of("insert", "MySQL", "INSERT INTO orders VALUES (1)", SupportedMCPStatement.DML, "INSERT", "INSERT INTO orders VALUES (1)", "orders", ""),
+                Arguments.of("update", "MySQL", "UPDATE orders SET status = 'DONE'", SupportedMCPStatement.DML, "UPDATE", "UPDATE orders SET status = 'DONE'", "orders", ""),
+                Arguments.of("delete", "MySQL", "DELETE FROM orders WHERE order_id = 1", SupportedMCPStatement.DML, "DELETE",
+                        "DELETE FROM orders WHERE order_id = 1", "orders", ""),
+                Arguments.of("merge", "Oracle", "MERGE INTO order_archive USING orders ON (order_archive.order_id = orders.order_id)", SupportedMCPStatement.DML, "MERGE",
+                        "MERGE INTO order_archive USING orders ON (order_archive.order_id = orders.order_id)", "order_archive", ""),
+                Arguments.of("create table", "MySQL", "CREATE TABLE order_archive(order_id BIGINT)", SupportedMCPStatement.DDL, "CREATE",
+                        "CREATE TABLE order_archive(order_id BIGINT)", "order_archive", ""),
+                Arguments.of("create index", "PostgreSQL", "CREATE INDEX order_idx ON orders (status)", SupportedMCPStatement.DDL, "CREATE",
+                        "CREATE INDEX order_idx ON orders (status)", "order_idx", ""),
+                Arguments.of("alter table", "MySQL", "ALTER TABLE orders ADD COLUMN status VARCHAR(10)", SupportedMCPStatement.DDL, "ALTER",
+                        "ALTER TABLE orders ADD COLUMN status VARCHAR(10)", "orders", ""),
+                Arguments.of("drop table", "MySQL", "DROP TABLE orders", SupportedMCPStatement.DDL, "DROP", "DROP TABLE orders", "orders", ""),
+                Arguments.of("truncate table", "MySQL", "TRUNCATE TABLE orders", SupportedMCPStatement.DDL, "TRUNCATE", "TRUNCATE TABLE orders", "orders", ""),
+                Arguments.of("grant", "MySQL", "GRANT SELECT ON orders TO PUBLIC", SupportedMCPStatement.DCL, "GRANT", "GRANT SELECT ON orders TO PUBLIC", "orders", ""),
+                Arguments.of("revoke", "MySQL", "REVOKE SELECT ON orders FROM PUBLIC", SupportedMCPStatement.DCL, "REVOKE", "REVOKE SELECT ON orders FROM PUBLIC", "orders", ""),
+                Arguments.of("begin", "MySQL", "BEGIN", SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "BEGIN", "", ""),
+                Arguments.of("start transaction", "MySQL", "START TRANSACTION", SupportedMCPStatement.TRANSACTION_CONTROL, "START TRANSACTION", "START TRANSACTION", "", ""),
+                Arguments.of("commit", "MySQL", "COMMIT", SupportedMCPStatement.TRANSACTION_CONTROL, "COMMIT", "COMMIT", "", ""),
+                Arguments.of("rollback", "MySQL", "ROLLBACK", SupportedMCPStatement.TRANSACTION_CONTROL, "ROLLBACK", "ROLLBACK", "", ""),
+                Arguments.of("savepoint", "MySQL", "SAVEPOINT order_sp", SupportedMCPStatement.SAVEPOINT, "SAVEPOINT", "SAVEPOINT order_sp", "", "order_sp"),
+                Arguments.of("rollback to without optional keyword", "MySQL", "ROLLBACK TO order_sp", SupportedMCPStatement.SAVEPOINT, "ROLLBACK TO",
+                        "ROLLBACK TO order_sp", "", "order_sp"),
+                Arguments.of("rollback to", "MySQL", "ROLLBACK TO SAVEPOINT order_sp", SupportedMCPStatement.SAVEPOINT, "ROLLBACK TO SAVEPOINT",
+                        "ROLLBACK TO SAVEPOINT order_sp", "", "order_sp"),
+                Arguments.of("release savepoint", "MySQL", "RELEASE SAVEPOINT order_sp", SupportedMCPStatement.SAVEPOINT, "RELEASE SAVEPOINT",
+                        "RELEASE SAVEPOINT order_sp", "", "order_sp"));
+    }
+    
+    private static Stream<Arguments> invalidStatementCases() {
+        return Stream.of(
+                Arguments.of("blank", "MySQL", "   ", MCPInvalidRequestException.class, "sql cannot be empty."),
+                Arguments.of("delimiter only", "MySQL", ";", MCPInvalidRequestException.class, "sql cannot be empty."),
+                Arguments.of("multiple statements", "MySQL", "SELECT 1; SELECT 2", MCPMultipleSQLStatementsException.class, "Only one SQL statement is allowed."),
+                Arguments.of("parser unavailable", "H2", "SELECT 1", MCPUnsupportedException.class, "SQL parser is not available for database type `H2`."),
+                Arguments.of("savepoint without name", "MySQL", "SAVEPOINT", MCPInvalidRequestException.class, "Savepoint name is required."),
+                Arguments.of("savepoint with extra token", "MySQL", "SAVEPOINT order_sp extra", MCPInvalidRequestException.class, "Savepoint name is required."),
+                Arguments.of("release savepoint without name", "MySQL", "RELEASE SAVEPOINT", MCPInvalidRequestException.class, "Savepoint name is required."),
+                Arguments.of("release savepoint with extra token", "MySQL", "RELEASE SAVEPOINT order_sp extra", MCPInvalidRequestException.class,
+                        "Savepoint name is required."),
+                Arguments.of("rollback to without name", "MySQL", "ROLLBACK TO", MCPInvalidRequestException.class, "Savepoint name is required."),
+                Arguments.of("rollback to savepoint without name", "MySQL", "ROLLBACK TO SAVEPOINT", MCPInvalidRequestException.class, "Savepoint name is required."),
+                Arguments.of("rollback to savepoint with extra token", "MySQL", "ROLLBACK TO SAVEPOINT order_sp extra", MCPInvalidRequestException.class,
+                        "Savepoint name is required."),
+                Arguments.of("banned use", "MySQL", "USE other_db", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
+                Arguments.of("banned set", "PostgreSQL", "SET search_path public", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
+                Arguments.of("banned copy", "PostgreSQL", "COPY orders FROM '/tmp/orders.csv'", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
+                Arguments.of("banned load", "MySQL", "LOAD DATA INFILE '/tmp/orders.csv' INTO TABLE orders", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned call", "MySQL", "CALL refresh_orders()", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
+                Arguments.of("banned select into outfile", "MySQL", "SELECT * FROM orders INTO OUTFILE '/tmp/orders.csv'", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned executable comment", "MySQL", "SELECT 1 /*!50000 UNION SELECT 2 */", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned MariaDB executable comment", "MariaDB", "SELECT 1 /*M!100000 UNION SELECT 2 */", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned nextval", "PostgreSQL", "SELECT nextval('order_seq')", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned setval", "PostgreSQL", "SELECT pg_catalog.setval('order_seq', 1)", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned next value for", "SQLServer", "SELECT NEXT VALUE FOR order_seq", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned nextval pseudocolumn", "Oracle", "SELECT order_seq.NEXTVAL FROM dual", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned named lock", "MySQL", "SELECT GET_LOCK('order_lock', 1)", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned named lock release", "MySQL", "SELECT RELEASE_LOCK('order_lock')", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned advisory lock", "PostgreSQL", "SELECT pg_advisory_lock(1)", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned configuration mutation", "PostgreSQL", "SELECT set_config('search_path', 'public', false)", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned replication slot advance", "PostgreSQL", "SELECT pg_replication_slot_advance('order_slot', '0/1')",
+                        MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
+                Arguments.of("banned wal switch", "PostgreSQL", "SELECT pg_switch_wal()", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned backend cancellation", "PostgreSQL", "SELECT pg_cancel_backend(1)", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned user variable assignment", "MySQL", "SELECT @order_status := status FROM orders", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned last insert id mutation", "MySQL", "SELECT LAST_INSERT_ID(1)", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned metadata lookup", "PostgreSQL", "SELECT to_regclass('orders')", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned alter system", "PostgreSQL", "ALTER SYSTEM SET shared_buffers = '128MB'", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned create user", "MySQL", "CREATE USER app_user IDENTIFIED BY 'pwd'", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("banned alter role", "PostgreSQL", "ALTER ROLE app_role SET search_path = public", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("select into", "PostgreSQL", "SELECT * INTO order_archive FROM orders", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("select into variable", "PostgreSQL", "SELECT COUNT(*) INTO order_count FROM orders", MCPBannedSQLStatementException.class,
+                        "Statement is banned by the MCP contract."),
+                Arguments.of("with select into", "PostgreSQL", "WITH order_result AS (SELECT * FROM orders) SELECT * INTO order_archive FROM order_result",
+                        MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
+                Arguments.of("locking read", "PostgreSQL", "SELECT * FROM orders FOR UPDATE", MCPLockingReadStatementException.class,
+                        "Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."),
+                Arguments.of("locking read for no key update", "PostgreSQL", "SELECT * FROM orders FOR NO KEY UPDATE", MCPLockingReadStatementException.class,
+                        "Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."),
+                Arguments.of("locking read in share mode", "MySQL", "SELECT * FROM orders LOCK IN SHARE MODE", MCPLockingReadStatementException.class,
+                        "Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."),
+                Arguments.of("nested locking read", "MySQL", "SELECT * FROM (SELECT * FROM orders FOR UPDATE) locked_orders", MCPLockingReadStatementException.class,
+                        "Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."),
+                Arguments.of("metadata show", "MySQL", "SHOW TABLES", MetadataIntrospectionSQLStatementException.class,
+                        "Metadata introspection SQL should use MCP metadata resources."),
+                Arguments.of("explain", "MySQL", "EXPLAIN SELECT * FROM orders", MCPUnsupportedSQLStatementException.class,
+                        "Statement is not supported by the MCP contract."),
+                Arguments.of("start transaction options", "PostgreSQL", "START TRANSACTION READ ONLY", MCPUnsupportedSQLStatementException.class,
+                        "Statement is not supported by the MCP contract."),
+                Arguments.of("wrong dialect", "PostgreSQL", "SELECT * FROM orders LIMIT 1, 2", MCPUnsupportedSQLStatementException.class,
+                        "Statement is not supported by the MCP contract."),
+                Arguments.of("data modifying cte", "PostgreSQL", "WITH updated AS (UPDATE orders SET status = 'DONE' RETURNING *) SELECT * FROM updated",
+                        MCPUnsupportedSQLStatementException.class, "Statement is not supported by the MCP contract."),
+                Arguments.of("unsupported dal", "MySQL", "ANALYZE TABLE orders", MCPUnsupportedSQLStatementException.class,
+                        "Statement is not supported by the MCP contract."));
+    }
+    
+    private static Stream<Arguments> metadataStatementCases() {
+        return Stream.of(
+                Arguments.of("show storage units", "SHOW STORAGE UNITS FROM logic_db", "SHOW STORAGE UNITS"),
+                Arguments.of("show rules used storage unit", "SHOW RULES USED STORAGE UNIT write_ds FROM logic_db", "SHOW RULES USED STORAGE UNIT"),
+                Arguments.of("show single tables", "SHOW SINGLE TABLES FROM logic_db", "SHOW SINGLE TABLES"),
+                Arguments.of("show single table", "SHOW SINGLE TABLE orders FROM logic_db", "SHOW SINGLE TABLE"),
+                Arguments.of("show default single table storage unit", "SHOW DEFAULT SINGLE TABLE STORAGE UNIT FROM logic_db",
+                        "SHOW DEFAULT SINGLE TABLE STORAGE UNIT"),
+                Arguments.of("describe", "DESCRIBE orders", "DESCRIBE"),
+                Arguments.of("desc", "DESC orders", "DESC"));
+    }
+}
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/RuleDistSQLExecutionExceptionTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/RuleDistSQLExecutionExceptionTest.java
index 777c0e1d3ce..f637bf126e7 100644
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/RuleDistSQLExecutionExceptionTest.java
+++ b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/RuleDistSQLExecutionExceptionTest.java
@@ -31,7 +31,7 @@ class RuleDistSQLExecutionExceptionTest {
     @Test
     void assertRuleDistSQLExecutionException() {
         ClassificationResult classificationResult = new ClassificationResult(SupportedMCPStatement.DDL, "CREATE",
-                "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'))", "", List.of());
+                "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'))", "", List.of(), true);
         SQLSyntaxErrorException cause = new SQLSyntaxErrorException("syntax error");
         RuleDistSQLExecutionException actual = new RuleDistSQLExecutionException("sharding_db", classificationResult, cause);
         assertThat(actual.getMessage(),
diff --git a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/StatementClassifierTest.java b/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/StatementClassifierTest.java
deleted file mode 100644
index 5150b035fcb..00000000000
--- a/mcp/core/src/test/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/StatementClassifierTest.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * 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.core.tool.handler.execute;
-
-import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPBannedSQLStatementException;
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPLockingReadStatementException;
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPMultipleSQLStatementsException;
-import org.apache.shardingsphere.mcp.core.protocol.exception.MCPUnsupportedSQLStatementException;
-import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import java.util.stream.Stream;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-class StatementClassifierTest {
-    
-    private final StatementClassifier statementClassifier = new StatementClassifier();
-    
-    @ParameterizedTest(name = "{0}")
-    @MethodSource("assertClassifyCases")
-    void assertClassify(final String name, final String sql, final SupportedMCPStatement expectedStatementClass, final String expectedStatementType,
-                        final String expectedNormalizedSql, final String expectedTargetObjectName, final String expectedSavepointName) {
-        ClassificationResult actualResult = statementClassifier.classify(sql);
-        assertThat(actualResult.getStatementClass(), is(expectedStatementClass));
-        assertThat(actualResult.getStatementType(), is(expectedStatementType));
-        assertThat(actualResult.getNormalizedSql(), is(expectedNormalizedSql));
-        assertThat(actualResult.getTargetObjectName().orElse(""), is(expectedTargetObjectName));
-        assertThat(actualResult.getSavepointName().orElse(""), is(expectedSavepointName));
-    }
-    
-    @ParameterizedTest(name = "{0}")
-    @MethodSource("assertClassifyWithInvalidStatementCases")
-    void assertClassifyWithInvalidStatement(final String name, final String sql, final Class<? extends RuntimeException> expectedExceptionClass, final String expectedMessage) {
-        RuntimeException actualException = assertThrows(expectedExceptionClass, () -> statementClassifier.classify(sql));
-        assertThat(actualException.getMessage(), is(expectedMessage));
-    }
-    
-    @ParameterizedTest(name = "{0}")
-    @MethodSource("assertClassifyMetadataStatementTypeCases")
-    void assertClassifyMetadataStatementType(final String name, final String sql, final String expectedStatementType) {
-        MetadataIntrospectionSQLStatementException actual = assertThrows(MetadataIntrospectionSQLStatementException.class, () -> statementClassifier.classify(sql));
-        assertThat(actual.getStatementType(), is(expectedStatementType));
-    }
-    
-    @Test
-    void assertClassifyReferencedObjectNames() {
-        ClassificationResult actualResult = statementClassifier.classify(
-                "SELECT * FROM logic_db.foo_orders JOIN other_db.foo_order_items ON foo_orders.order_id = foo_order_items.order_id");
-        assertThat(actualResult.getReferencedObjectNames(), contains("logic_db.foo_orders", "other_db.foo_order_items"));
-    }
-    
-    @Test
-    void assertClassifyDMLReferencedObjectNames() {
-        ClassificationResult actualResult = statementClassifier.classify("UPDATE logic_db.foo_orders SET status = 'DONE' FROM other_db.foo_order_items");
-        assertThat(actualResult.getReferencedObjectNames(), contains("logic_db.foo_orders", "other_db.foo_order_items"));
-    }
-    
-    @Test
-    void assertClassifyRuleDistSQLSideEffectScope() {
-        ClassificationResult actualResult = statementClassifier.classify(
-                "CREATE SHARDING TABLE RULE t_order(DATANODES('ds_${0..1}.t_order_${0..1}'), KEY_GENERATE_STRATEGY(COLUMN=id, TYPE(NAME='snowflake')))");
-        assertTrue(actualResult.isRuleDistSQL());
-        assertThat(actualResult.getSideEffectScope(), is("rule-metadata"));
-    }
-    
-    @Test
-    void assertClassifyPhysicalDDLSideEffectScope() {
-        ClassificationResult actualResult = statementClassifier.classify("CREATE TABLE foo_orders(order_id BIGINT)");
-        assertFalse(actualResult.isRuleDistSQL());
-        assertThat(actualResult.getSideEffectScope(), is("physical-structure"));
-    }
-    
-    @Test
-    void assertClassifySubqueryReferencedObjectNames() {
-        ClassificationResult actualResult = statementClassifier.classify(
-                "SELECT * FROM logic_db.foo_orders WHERE EXISTS (SELECT 1 FROM other_db.foo_order_items)");
-        assertThat(actualResult.getReferencedObjectNames(), contains("logic_db.foo_orders", "other_db.foo_order_items"));
-    }
-    
-    @Test
-    void assertClassifyDerivedTableReferencedObjectNames() {
-        ClassificationResult actualResult = statementClassifier.classify("SELECT * FROM (SELECT * FROM other_db.foo_order_items) foo_items");
-        assertThat(actualResult.getReferencedObjectNames(), contains("other_db.foo_order_items"));
-    }
-    
-    @Test
-    void assertClassifyDMLSubqueryReferencedObjectNames() {
-        ClassificationResult actualResult = statementClassifier.classify(
-                "UPDATE logic_db.foo_orders SET status = 'DONE' WHERE EXISTS (SELECT 1 FROM other_db.foo_order_items)");
-        assertThat(actualResult.getReferencedObjectNames(), contains("logic_db.foo_orders", "other_db.foo_order_items"));
-    }
-    
-    @ParameterizedTest(name = "{0}")
-    @MethodSource("assertClassifyReferencedObjectNamesWithObjectListsCases")
-    void assertClassifyReferencedObjectNamesWithObjectLists(final String name, final String sql, final String[] expectedReferencedObjectNames) {
-        ClassificationResult actualResult = statementClassifier.classify(sql);
-        assertThat(actualResult.getReferencedObjectNames(), contains(expectedReferencedObjectNames));
-    }
-    
-    private static Stream<Arguments> assertClassifyCases() {
-        return Stream.of(
-                Arguments.of("trim trailing semicolon query", "  SELECT * FROM foo_orders ;  ", SupportedMCPStatement.QUERY, "SELECT", "SELECT * FROM foo_orders", "foo_orders", ""),
-                Arguments.of("query with semicolon literal", "SELECT ';' AS foo_literal", SupportedMCPStatement.QUERY, "SELECT", "SELECT ';' AS foo_literal", "", ""),
-                Arguments.of("query with semicolon block comment", "SELECT * FROM foo_orders /* source; */", SupportedMCPStatement.QUERY, "SELECT",
-                        "SELECT * FROM foo_orders /* source; */", "foo_orders", ""),
-                Arguments.of("trim trailing semicolon query with comment", "SELECT * FROM foo_orders; -- trailing;", SupportedMCPStatement.QUERY, "SELECT",
-                        "SELECT * FROM foo_orders", "foo_orders", ""),
-                Arguments.of("query from values target omitted", "SELECT * FROM (VALUES (1)) foo_result", SupportedMCPStatement.QUERY, "SELECT",
-                        "SELECT * FROM (VALUES (1)) foo_result", "", ""),
-                Arguments.of("with query", "WITH foo_result AS (SELECT * FROM foo_orders) SELECT * FROM foo_result", SupportedMCPStatement.QUERY, "SELECT",
-                        "WITH foo_result AS (SELECT * FROM foo_orders) SELECT * FROM foo_result", "foo_orders", ""),
-                Arguments.of("with recursive query", "WITH RECURSIVE foo_result AS (SELECT * FROM foo_orders) SELECT * FROM foo_result", SupportedMCPStatement.QUERY, "SELECT",
-                        "WITH RECURSIVE foo_result AS (SELECT * FROM foo_orders) SELECT * FROM foo_result", "foo_orders", ""),
-                Arguments.of("with quoted alias query", "WITH \"foo_result\" AS (SELECT * FROM \"foo_orders\") SELECT * FROM \"foo_result\"", SupportedMCPStatement.QUERY, "SELECT",
-                        "WITH \"foo_result\" AS (SELECT * FROM \"foo_orders\") SELECT * FROM \"foo_result\"", "foo_orders", ""),
-                Arguments.of("with comment query", "WITH foo_result AS (SELECT * FROM foo_orders /* source */) SELECT * FROM foo_result", SupportedMCPStatement.QUERY, "SELECT",
-                        "WITH foo_result AS (SELECT * FROM foo_orders /* source */) SELECT * FROM foo_result", "foo_orders", ""),
-                Arguments.of("with update dml", "WITH foo_result AS (SELECT * FROM foo_orders) UPDATE bar_orders SET status = 'DONE' FROM foo_result WHERE bar_orders.order_id = foo_result.order_id",
-                        SupportedMCPStatement.DML, "UPDATE",
-                        "WITH foo_result AS (SELECT * FROM foo_orders) UPDATE bar_orders SET status = 'DONE' FROM foo_result WHERE bar_orders.order_id = foo_result.order_id",
-                        "bar_orders", ""),
-                Arguments.of("with merge dml", "WITH foo_result AS (SELECT * FROM foo_orders) MERGE INTO bar_orders_archive USING foo_result ON 1 = 1",
-                        SupportedMCPStatement.DML, "MERGE",
-                        "WITH foo_result AS (SELECT * FROM foo_orders) MERGE INTO bar_orders_archive USING foo_result ON 1 = 1", "bar_orders_archive", ""),
-                Arguments.of("data modifying cte select", "WITH updated_orders AS (UPDATE foo_orders SET status = 'DONE' RETURNING *) SELECT * FROM updated_orders",
-                        SupportedMCPStatement.DML, "SELECT",
-                        "WITH updated_orders AS (UPDATE foo_orders SET status = 'DONE' RETURNING *) SELECT * FROM updated_orders", "foo_orders", ""),
-                Arguments.of("nested data modifying cte select",
-                        "WITH foo_result AS (WITH updated_orders AS (UPDATE foo_orders SET status = 'DONE' RETURNING *) SELECT * FROM updated_orders) SELECT * FROM foo_result",
-                        SupportedMCPStatement.DML, "SELECT",
-                        "WITH foo_result AS (WITH updated_orders AS (UPDATE foo_orders SET status = 'DONE' RETURNING *) SELECT * FROM updated_orders) SELECT * FROM foo_result",
-                        "foo_orders", ""),
-                Arguments.of("insert dml", "INSERT INTO foo_orders VALUES (1)", SupportedMCPStatement.DML, "INSERT",
-                        "INSERT INTO foo_orders VALUES (1)", "foo_orders", ""),
-                Arguments.of("insert select dml", "INSERT INTO bar_orders_archive SELECT * FROM foo_orders", SupportedMCPStatement.DML, "INSERT",
-                        "INSERT INTO bar_orders_archive SELECT * FROM foo_orders", "bar_orders_archive", ""),
-                Arguments.of("update dml", "UPDATE foo_orders SET status = 'DONE'", SupportedMCPStatement.DML, "UPDATE", "UPDATE foo_orders SET status = 'DONE'", "foo_orders", ""),
-                Arguments.of("delete only dml", "DELETE FROM ONLY foo_orders WHERE order_id = 1", SupportedMCPStatement.DML, "DELETE",
-                        "DELETE FROM ONLY foo_orders WHERE order_id = 1", "foo_orders", ""),
-                Arguments.of("delete dml", "DELETE FROM foo_orders WHERE order_id = 1", SupportedMCPStatement.DML, "DELETE",
-                        "DELETE FROM foo_orders WHERE order_id = 1", "foo_orders", ""),
-                Arguments.of("merge dml", "MERGE INTO bar_orders_archive USING foo_orders ON 1 = 1", SupportedMCPStatement.DML, "MERGE",
-                        "MERGE INTO bar_orders_archive USING foo_orders ON 1 = 1", "bar_orders_archive", ""),
-                Arguments.of("create table ddl", "CREATE TABLE bar_orders_archive", SupportedMCPStatement.DDL, "CREATE", "CREATE TABLE bar_orders_archive", "bar_orders_archive", ""),
-                Arguments.of("create table if not exists ddl", "CREATE TABLE IF NOT EXISTS bar_orders_archive", SupportedMCPStatement.DDL, "CREATE",
-                        "CREATE TABLE IF NOT EXISTS bar_orders_archive", "bar_orders_archive", ""),
-                Arguments.of("create view ddl", "CREATE VIEW bar_active_orders AS SELECT * FROM foo_orders", SupportedMCPStatement.DDL, "CREATE",
-                        "CREATE VIEW bar_active_orders AS SELECT * FROM foo_orders", "bar_active_orders", ""),
-                Arguments.of("create or replace view ddl", "CREATE OR REPLACE VIEW bar_active_orders AS SELECT * FROM foo_orders", SupportedMCPStatement.DDL, "CREATE",
-                        "CREATE OR REPLACE VIEW bar_active_orders AS SELECT * FROM foo_orders", "bar_active_orders", ""),
-                Arguments.of("alter ddl", "ALTER TABLE foo_orders ADD COLUMN status VARCHAR(10)", SupportedMCPStatement.DDL, "ALTER",
-                        "ALTER TABLE foo_orders ADD COLUMN status VARCHAR(10)", "foo_orders", ""),
-                Arguments.of("alter table only ddl", "ALTER TABLE ONLY foo_orders ADD COLUMN status VARCHAR(10)", SupportedMCPStatement.DDL, "ALTER",
-                        "ALTER TABLE ONLY foo_orders ADD COLUMN status VARCHAR(10)", "foo_orders", ""),
-                Arguments.of("drop ddl", "DROP TABLE foo_orders", SupportedMCPStatement.DDL, "DROP", "DROP TABLE foo_orders", "foo_orders", ""),
-                Arguments.of("drop table if exists ddl", "DROP TABLE IF EXISTS foo_orders", SupportedMCPStatement.DDL, "DROP", "DROP TABLE IF EXISTS foo_orders", "foo_orders", ""),
-                Arguments.of("truncate ddl", "TRUNCATE TABLE foo_orders", SupportedMCPStatement.DDL, "TRUNCATE", "TRUNCATE TABLE foo_orders", "foo_orders", ""),
-                Arguments.of("grant dcl", "GRANT SELECT ON foo_orders TO PUBLIC", SupportedMCPStatement.DCL, "GRANT", "GRANT SELECT ON foo_orders TO PUBLIC", "foo_orders", ""),
-                Arguments.of("revoke dcl", "REVOKE SELECT ON foo_orders FROM PUBLIC", SupportedMCPStatement.DCL, "REVOKE", "REVOKE SELECT ON foo_orders FROM PUBLIC", "foo_orders", ""),
-                Arguments.of("begin transaction", "BEGIN", SupportedMCPStatement.TRANSACTION_CONTROL, "BEGIN", "BEGIN", "", ""),
-                Arguments.of("start transaction", "START TRANSACTION", SupportedMCPStatement.TRANSACTION_CONTROL, "START TRANSACTION", "START TRANSACTION", "", ""),
-                Arguments.of("commit transaction", "COMMIT", SupportedMCPStatement.TRANSACTION_CONTROL, "COMMIT", "COMMIT", "", ""),
-                Arguments.of("rollback transaction", "ROLLBACK", SupportedMCPStatement.TRANSACTION_CONTROL, "ROLLBACK", "ROLLBACK", "", ""),
-                Arguments.of("savepoint", "SAVEPOINT foo_sp_1", SupportedMCPStatement.SAVEPOINT, "SAVEPOINT", "SAVEPOINT foo_sp_1", "", "foo_sp_1"),
-                Arguments.of("release savepoint", "RELEASE SAVEPOINT foo_sp_1", SupportedMCPStatement.SAVEPOINT, "RELEASE SAVEPOINT",
-                        "RELEASE SAVEPOINT foo_sp_1", "", "foo_sp_1"),
-                Arguments.of("rollback to savepoint", "ROLLBACK TO SAVEPOINT foo_sp_1", SupportedMCPStatement.SAVEPOINT, "ROLLBACK TO SAVEPOINT",
-                        "ROLLBACK TO SAVEPOINT foo_sp_1", "", "foo_sp_1"),
-                Arguments.of("rollback to savepoint name without optional keyword", "ROLLBACK TO foo_sp_1", SupportedMCPStatement.SAVEPOINT, "ROLLBACK TO",
-                        "ROLLBACK TO foo_sp_1", "", "foo_sp_1"));
-    }
-    
-    private static Stream<Arguments> assertClassifyReferencedObjectNamesWithObjectListsCases() {
-        return Stream.of(
-                Arguments.of("query from object list", "SELECT * FROM logic_db.foo_orders, other_db.foo_order_items",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("query from aliased object list", "SELECT * FROM logic_db.foo_orders o, other_db.foo_order_items i",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("query from partitioned object list", "SELECT * FROM logic_db.foo_orders PARTITION (p0) o, other_db.foo_order_items i",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("query from index hinted object list", "SELECT * FROM logic_db.foo_orders FORCE INDEX (idx_status), other_db.foo_order_items",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("cte query from object list", "WITH foo_result AS (SELECT * FROM logic_db.foo_orders, other_db.foo_order_items) SELECT * FROM foo_result",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("unused cte query reference", "WITH unused_result AS (SELECT * FROM other_db.foo_unused_orders) SELECT * FROM logic_db.foo_orders",
-                        new String[]{"other_db.foo_unused_orders", "logic_db.foo_orders"}),
-                Arguments.of("insert select from object list", "INSERT INTO logic_db.foo_orders_archive SELECT * FROM logic_db.foo_orders, other_db.foo_order_items",
-                        new String[]{"logic_db.foo_orders_archive", "logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("update target object list", "UPDATE logic_db.foo_orders, other_db.foo_order_items SET status = 'DONE'",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("update target aliased object list", "UPDATE logic_db.foo_orders o, other_db.foo_order_items i SET o.status = 'DONE'",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("delete target before from", "DELETE other_db.foo_orders FROM logic_db.foo_orders JOIN other_db.foo_order_items ON 1 = 1",
-                        new String[]{"other_db.foo_orders", "logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("delete target object list before from", "DELETE logic_db.foo_orders, other_db.foo_order_items FROM logic_db.foo_orders JOIN other_db.foo_order_items ON 1 = 1",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("delete using object list", "DELETE FROM logic_db.foo_orders USING other_db.foo_order_items, other_db.foo_order_payments",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items", "other_db.foo_order_payments"}),
-                Arguments.of("create view from object list", "CREATE VIEW logic_db.foo_active_orders AS SELECT * FROM logic_db.foo_orders, other_db.foo_order_items",
-                        new String[]{"logic_db.foo_active_orders", "logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("create table like source", "CREATE TABLE logic_db.foo_orders_archive LIKE other_db.foo_orders",
-                        new String[]{"logic_db.foo_orders_archive", "other_db.foo_orders"}),
-                Arguments.of("create index on source", "CREATE INDEX foo_orders_idx ON other_db.foo_orders (status)",
-                        new String[]{"foo_orders_idx", "other_db.foo_orders"}),
-                Arguments.of("create index using method", "CREATE INDEX foo_orders_idx ON logic_db.foo_orders USING btree (status)",
-                        new String[]{"foo_orders_idx", "logic_db.foo_orders"}),
-                Arguments.of("create index with modifiers", "CREATE INDEX CONCURRENTLY IF NOT EXISTS other_db.foo_orders_idx ON logic_db.foo_orders (status)",
-                        new String[]{"other_db.foo_orders_idx", "logic_db.foo_orders"}),
-                Arguments.of("drop index on source", "DROP INDEX foo_orders_idx ON other_db.foo_orders",
-                        new String[]{"foo_orders_idx", "other_db.foo_orders"}),
-                Arguments.of("drop index with modifiers", "DROP INDEX CONCURRENTLY IF EXISTS other_db.foo_orders_idx",
-                        new String[]{"other_db.foo_orders_idx"}),
-                Arguments.of("drop table object list", "DROP TABLE IF EXISTS logic_db.foo_orders, other_db.foo_order_items",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("alter table with modifiers", "ALTER TABLE IF EXISTS other_db.foo_orders ADD COLUMN status VARCHAR(10)",
-                        new String[]{"other_db.foo_orders"}),
-                Arguments.of("alter table rename destination", "ALTER TABLE logic_db.foo_orders RENAME TO other_db.foo_orders_archive",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_orders_archive"}),
-                Arguments.of("alter table set schema destination", "ALTER TABLE logic_db.foo_orders SET SCHEMA other_db",
-                        new String[]{"logic_db.foo_orders", "other_db"}),
-                Arguments.of("create database target", "CREATE DATABASE other_db",
-                        new String[]{"other_db"}),
-                Arguments.of("drop schema target", "DROP SCHEMA IF EXISTS other_db",
-                        new String[]{"other_db"}),
-                Arguments.of("grant global wildcard", "GRANT SELECT ON *.* TO PUBLIC",
-                        new String[]{"*.*"}),
-                Arguments.of("grant database target", "GRANT CONNECT ON DATABASE other_db TO PUBLIC",
-                        new String[]{"other_db"}),
-                Arguments.of("truncate table object list", "TRUNCATE TABLE logic_db.foo_orders, other_db.foo_order_items",
-                        new String[]{"logic_db.foo_orders", "other_db.foo_order_items"}),
-                Arguments.of("qualified function query", "SELECT other_db.foo_refresh_orders()",
-                        new String[]{"other_db.foo_refresh_orders"}));
-    }
-    
-    private static Stream<Arguments> assertClassifyMetadataStatementTypeCases() {
-        return Stream.of(
-                Arguments.of("show storage units", "SHOW STORAGE UNITS FROM logic_db", "SHOW STORAGE UNITS"),
-                Arguments.of("show rules used storage unit", "SHOW RULES USED STORAGE UNIT write_ds FROM logic_db", "SHOW RULES USED STORAGE UNIT"),
-                Arguments.of("show single tables", "SHOW SINGLE TABLES FROM logic_db", "SHOW SINGLE TABLES"),
-                Arguments.of("show single table", "SHOW SINGLE TABLE t_user FROM logic_db", "SHOW SINGLE TABLE"),
-                Arguments.of("show default single table storage unit", "SHOW DEFAULT SINGLE TABLE STORAGE UNIT FROM logic_db", "SHOW DEFAULT SINGLE TABLE STORAGE UNIT"));
-    }
-    
-    private static Stream<Arguments> assertClassifyWithInvalidStatementCases() {
-        return Stream.of(
-                Arguments.of("blank sql", "   ", MCPInvalidRequestException.class, "sql cannot be empty."),
-                Arguments.of("statement delimiter only", ";", MCPInvalidRequestException.class, "sql cannot be empty."),
-                Arguments.of("multiple statements", "SELECT 1; SELECT 2", MCPMultipleSQLStatementsException.class, "Only one SQL statement is allowed."),
-                Arguments.of("savepoint without name", "SAVEPOINT", MCPInvalidRequestException.class, "Savepoint name is required."),
-                Arguments.of("savepoint with extra token", "SAVEPOINT foo_sp_1 extra", MCPInvalidRequestException.class, "Savepoint name is required."),
-                Arguments.of("release savepoint without name", "RELEASE SAVEPOINT", MCPInvalidRequestException.class, "Savepoint name is required."),
-                Arguments.of("release savepoint with extra token", "RELEASE SAVEPOINT foo_sp_1 extra", MCPInvalidRequestException.class, "Savepoint name is required."),
-                Arguments.of("rollback to without name", "ROLLBACK TO", MCPInvalidRequestException.class, "Savepoint name is required."),
-                Arguments.of("rollback to savepoint without name", "ROLLBACK TO SAVEPOINT", MCPInvalidRequestException.class, "Savepoint name is required."),
-                Arguments.of("rollback to savepoint with extra token", "ROLLBACK TO SAVEPOINT foo_sp_1 extra", MCPInvalidRequestException.class, "Savepoint name is required."),
-                Arguments.of("banned use", "USE foo_db", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned set", "SET search_path public", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned copy", "COPY foo_orders FROM '/tmp/foo.csv'", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned load", "LOAD DATA INFILE '/tmp/foo.csv' INTO TABLE foo_orders", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned call", "CALL foo_refresh_orders()", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned select into outfile", "SELECT * FROM foo_orders INTO OUTFILE '/tmp/foo.csv'", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned select into table", "SELECT * INTO bar_orders_archive FROM foo_orders", MCPBannedSQLStatementException.class,
-                        "Statement is banned by the MCP contract."),
-                Arguments.of("banned select into variable", "SELECT COUNT(*) INTO foo_count FROM foo_orders", MCPBannedSQLStatementException.class,
-                        "Statement is banned by the MCP contract."),
-                Arguments.of("banned with select into table", "WITH foo_result AS (SELECT * FROM foo_orders) SELECT * INTO bar_orders_archive FROM foo_result",
-                        MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned mysql executable comment", "SELECT * FROM logic_db.foo_orders /*!50000 JOIN other_db.foo_order_items ON 1 = 1 */",
-                        MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned mariadb executable comment", "SELECT * FROM logic_db.foo_orders /*M!100000 JOIN other_db.foo_order_items ON 1 = 1 */",
-                        MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned nextval function", "SELECT nextval('foo_seq')", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned setval function", "SELECT pg_catalog.setval('foo_seq', 1)", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned next value for sequence", "SELECT NEXT VALUE FOR foo_seq", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned sequence nextval pseudocolumn", "SELECT foo_seq.NEXTVAL FROM dual", MCPBannedSQLStatementException.class,
-                        "Statement is banned by the MCP contract."),
-                Arguments.of("banned get lock function", "SELECT GET_LOCK('foo_lock', 1)", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned release lock function", "SELECT RELEASE_LOCK('foo_lock')", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned advisory lock function", "SELECT pg_advisory_lock(1)", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned set config function", "SELECT set_config('search_path', 'public', false)", MCPBannedSQLStatementException.class,
-                        "Statement is banned by the MCP contract."),
-                Arguments.of("banned replication slot advance function", "SELECT pg_replication_slot_advance('foo_slot', '0/1')", MCPBannedSQLStatementException.class,
-                        "Statement is banned by the MCP contract."),
-                Arguments.of("banned wal switch function", "SELECT pg_switch_wal()", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned backend cancel function", "SELECT pg_cancel_backend(1)", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned mysql user variable assignment", "SELECT @foo_status := status FROM foo_orders", MCPBannedSQLStatementException.class,
-                        "Statement is banned by the MCP contract."),
-                Arguments.of("banned mysql last insert id mutation", "SELECT LAST_INSERT_ID(1)", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned metadata lookup function", "SELECT to_regclass('other_db.foo_orders')", MCPBannedSQLStatementException.class,
-                        "Statement is banned by the MCP contract."),
-                Arguments.of("start transaction read only", "START TRANSACTION READ ONLY", MCPUnsupportedSQLStatementException.class,
-                        "Statement is not supported by the MCP contract."),
-                Arguments.of("start transaction read write", "START TRANSACTION READ WRITE", MCPUnsupportedSQLStatementException.class,
-                        "Statement is not supported by the MCP contract."),
-                Arguments.of("start transaction isolation level", "START TRANSACTION ISOLATION LEVEL SERIALIZABLE", MCPUnsupportedSQLStatementException.class,
-                        "Statement is not supported by the MCP contract."),
-                Arguments.of("banned alter system", "ALTER SYSTEM SET shared_buffers = '128MB'", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned create user", "CREATE USER foo_user IDENTIFIED BY 'pwd'", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("banned alter role", "ALTER ROLE foo_role SET search_path = public", MCPBannedSQLStatementException.class, "Statement is banned by the MCP contract."),
-                Arguments.of("locking read for update", "SELECT * FROM foo_orders FOR UPDATE", MCPLockingReadStatementException.class,
-                        "Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."),
-                Arguments.of("locking read for no key update", "SELECT * FROM foo_orders FOR NO KEY UPDATE", MCPLockingReadStatementException.class,
-                        "Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."),
-                Arguments.of("locking read lock in share mode", "SELECT * FROM foo_orders LOCK IN SHARE MODE", MCPLockingReadStatementException.class,
-                        "Locking read statements such as SELECT ... FOR UPDATE are not supported by the MCP read-only contract."),
-                Arguments.of("explain query", "EXPLAIN SELECT * FROM foo_orders", MCPUnsupportedSQLStatementException.class, "Statement is not supported by the MCP contract."),
-                Arguments.of("explain locking read", "EXPLAIN SELECT * FROM foo_orders FOR SHARE", MCPUnsupportedSQLStatementException.class, "Statement is not supported by the MCP contract."),
-                Arguments.of("explain analyze", "EXPLAIN ANALYZE SELECT * FROM foo_orders", MCPUnsupportedSQLStatementException.class, "Statement is not supported by the MCP contract."),
-                Arguments.of("metadata show", "SHOW", MetadataIntrospectionSQLStatementException.class, "Metadata introspection SQL should use MCP metadata resources."),
-                Arguments.of("metadata show tables", "SHOW TABLES", MetadataIntrospectionSQLStatementException.class, "Metadata introspection SQL should use MCP metadata resources."),
-                Arguments.of("metadata describe", "DESCRIBE", MetadataIntrospectionSQLStatementException.class, "Metadata introspection SQL should use MCP metadata resources."),
-                Arguments.of("metadata describe table", "DESCRIBE foo_orders", MetadataIntrospectionSQLStatementException.class, "Metadata introspection SQL should use MCP metadata resources."),
-                Arguments.of("metadata desc", "DESC", MetadataIntrospectionSQLStatementException.class, "Metadata introspection SQL should use MCP metadata resources."),
-                Arguments.of("metadata desc table", "DESC foo_orders", MetadataIntrospectionSQLStatementException.class, "Metadata introspection SQL should use MCP metadata resources."),
-                Arguments.of("unsupported statement", "ANALYZE TABLE foo_orders", MCPUnsupportedSQLStatementException.class, "Statement is not supported by the MCP contract."));
-    }
-}
diff --git a/mcp/features/broadcast/pom.xml b/mcp/features/broadcast/pom.xml
index feb9faf8804..18d5af473f5 100644
--- a/mcp/features/broadcast/pom.xml
+++ b/mcp/features/broadcast/pom.xml
@@ -37,5 +37,11 @@
             <artifactId>shardingsphere-broadcast-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-broadcast-distsql-parser</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/mcp/features/encrypt/pom.xml b/mcp/features/encrypt/pom.xml
index c190371d072..228b381a730 100644
--- a/mcp/features/encrypt/pom.xml
+++ b/mcp/features/encrypt/pom.xml
@@ -37,5 +37,11 @@
             <artifactId>shardingsphere-encrypt-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-encrypt-distsql-parser</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/mcp/features/mask/pom.xml b/mcp/features/mask/pom.xml
index eed9875ac04..e8d20ff4331 100644
--- a/mcp/features/mask/pom.xml
+++ b/mcp/features/mask/pom.xml
@@ -37,5 +37,11 @@
             <artifactId>shardingsphere-mask-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-mask-distsql-parser</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/mcp/features/readwrite-splitting/pom.xml b/mcp/features/readwrite-splitting/pom.xml
index 3c80c6588b3..f5d05bd9cbd 100644
--- a/mcp/features/readwrite-splitting/pom.xml
+++ b/mcp/features/readwrite-splitting/pom.xml
@@ -42,5 +42,16 @@
             <artifactId>shardingsphere-infra-algorithm-load-balancer-spi</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-readwrite-splitting-distsql-statement</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-readwrite-splitting-distsql-parser</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/exception/MCPUnsupportedSQLStatementException.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/ReadwriteSplittingRuleDistSQLStatementClassifier.java
similarity index 53%
copy from mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/exception/MCPUnsupportedSQLStatementException.java
copy to mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/ReadwriteSplittingRuleDistSQLStatementClassifier.java
index 924ed09f5cf..431cdf2ec84 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/exception/MCPUnsupportedSQLStatementException.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/ReadwriteSplittingRuleDistSQLStatementClassifier.java
@@ -15,18 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.mcp.core.protocol.exception;
+package org.apache.shardingsphere.mcp.feature.readwritesplitting;
 
-import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnsupportedException;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPRuleDistSQLStatementClassifier;
+import org.apache.shardingsphere.readwritesplitting.distsql.statement.AlterReadwriteSplittingStorageUnitStatusStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
 
 /**
- * Exception for unsupported SQL statements.
+ * Rule DistSQL statement classifier for readwrite-splitting.
  */
-public final class MCPUnsupportedSQLStatementException extends MCPUnsupportedException {
+public final class ReadwriteSplittingRuleDistSQLStatementClassifier implements MCPRuleDistSQLStatementClassifier {
     
-    private static final long serialVersionUID = 8307677954728619722L;
-    
-    public MCPUnsupportedSQLStatementException() {
-        super("Statement is not supported by the MCP contract.");
+    @Override
+    public boolean isRuleDistSQL(final SQLStatement sqlStatement) {
+        return sqlStatement instanceof AlterReadwriteSplittingStorageUnitStatusStatement;
     }
 }
diff --git a/mcp/features/readwrite-splitting/src/main/resources/META-INF/services/org.apache.shardingsphere.mcp.support.database.spi.MCPRuleDistSQLStatementClassifier b/mcp/features/readwrite-splitting/src/main/resources/META-INF/services/org.apache.shardingsphere.mcp.support.database.spi.MCPRuleDistSQLStatementClassifier
new file mode 100644
index 00000000000..846bb964453
--- /dev/null
+++ b/mcp/features/readwrite-splitting/src/main/resources/META-INF/services/org.apache.shardingsphere.mcp.support.database.spi.MCPRuleDistSQLStatementClassifier
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.apache.shardingsphere.mcp.feature.readwritesplitting.ReadwriteSplittingRuleDistSQLStatementClassifier
diff --git a/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/ReadwriteSplittingRuleDistSQLStatementClassifierTest.java b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/ReadwriteSplittingRuleDistSQLStatementClassifierTest.java
new file mode 100644
index 00000000000..66fc046206d
--- /dev/null
+++ b/mcp/features/readwrite-splitting/src/test/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/ReadwriteSplittingRuleDistSQLStatementClassifierTest.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;
+
+import org.apache.shardingsphere.infra.spi.ShardingSphereServiceLoader;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPRuleDistSQLStatementClassifier;
+import org.apache.shardingsphere.readwritesplitting.distsql.statement.AlterReadwriteSplittingStorageUnitStatusStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dal.FromDatabaseSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+class ReadwriteSplittingRuleDistSQLStatementClassifierTest {
+    
+    private final ReadwriteSplittingRuleDistSQLStatementClassifier classifier = new ReadwriteSplittingRuleDistSQLStatementClassifier();
+    
+    @Test
+    void assertReadwriteSplittingStorageUnitStatusStatement() {
+        assertTrue(classifier.isRuleDistSQL(new AlterReadwriteSplittingStorageUnitStatusStatement(mock(FromDatabaseSegment.class), "readwrite_ds", "read_ds_0", true)));
+    }
+    
+    @Test
+    void assertOtherStatement() {
+        assertFalse(classifier.isRuleDistSQL(mock(SQLStatement.class)));
+    }
+    
+    @Test
+    void assertRegistered() {
+        assertThat(ShardingSphereServiceLoader.getServiceInstances(MCPRuleDistSQLStatementClassifier.class),
+                hasItem(isA(ReadwriteSplittingRuleDistSQLStatementClassifier.class)));
+    }
+}
diff --git a/mcp/features/shadow/pom.xml b/mcp/features/shadow/pom.xml
index 1997d1198ed..60cedcb4dc3 100644
--- a/mcp/features/shadow/pom.xml
+++ b/mcp/features/shadow/pom.xml
@@ -37,5 +37,11 @@
             <artifactId>shardingsphere-shadow-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-shadow-distsql-parser</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/mcp/features/sharding/pom.xml b/mcp/features/sharding/pom.xml
index f7b75354ee8..dcac59318c3 100644
--- a/mcp/features/sharding/pom.xml
+++ b/mcp/features/sharding/pom.xml
@@ -42,5 +42,11 @@
             <artifactId>shardingsphere-infra-algorithm-key-generator-spi</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-sharding-distsql-parser</artifactId>
+            <version>${project.version}</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/mcp/support/pom.xml b/mcp/support/pom.xml
index bea1fd1dca0..720b3686e05 100644
--- a/mcp/support/pom.xml
+++ b/mcp/support/pom.xml
@@ -57,6 +57,11 @@
             <artifactId>shardingsphere-infra-util</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-parser-sql-statement-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>jakarta.validation</groupId>
             <artifactId>jakarta.validation-api</artifactId>
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLCommonTableExpression.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/spi/MCPRuleDistSQLStatementClassifier.java
similarity index 57%
rename from mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLCommonTableExpression.java
rename to mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/spi/MCPRuleDistSQLStatementClassifier.java
index 889573e0e5c..cd06622cf8c 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/SQLCommonTableExpression.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/database/spi/MCPRuleDistSQLStatementClassifier.java
@@ -15,23 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.mcp.core.tool.handler.execute;
+package org.apache.shardingsphere.mcp.support.database.spi;
 
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.infra.spi.ShardingSphereSPI;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
 
-@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
-final class SQLCommonTableExpression {
-    
-    private final String aliasName;
-    
-    private final SQLStatementStructure statementStructure;
-    
-    String aliasName() {
-        return aliasName;
-    }
+/**
+ * Classifier for feature-specific rule DistSQL statements.
+ */
+public interface MCPRuleDistSQLStatementClassifier extends ShardingSphereSPI {
     
-    SQLStatementStructure statementStructure() {
-        return statementStructure;
-    }
+    /**
+     * Judge whether the SQL statement mutates rule metadata.
+     *
+     * @param sqlStatement SQL statement
+     * @return whether the SQL statement mutates rule metadata
+     */
+    boolean isRuleDistSQL(SQLStatement sqlStatement);
 }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java
index 9009a2b0a08..11449dc167e 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPGuidancePayloadBuilder.java
@@ -121,7 +121,7 @@ final class MCPGuidancePayloadBuilder {
         result.put("metadata_first_resource", "shardingsphere://databases");
         result.put("preflight_rule", "Use database_gateway_validate_runtime_database with a configured database name before onboarding or troubleshooting runtime connectivity.");
         Map<String, Object> sqlToolSelection = new LinkedHashMap<>(3, 1F);
-        sqlToolSelection.put("read_only", "Use database_gateway_execute_query for one classifier-approved SELECT statement.");
+        sqlToolSelection.put("read_only", "Use database_gateway_execute_query for one parser-approved SELECT statement.");
         sqlToolSelection.put("explain", "Use database_gateway_execute_explain_query with the original SELECT and a model-generated database-native EXPLAIN SQL; do not use EXPLAIN ANALYZE.");
         sqlToolSelection.put("side_effecting", "Use database_gateway_execute_update with execution_mode=preview before execution.");
         result.put("sql_tool_selection", sqlToolSelection);
diff --git a/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml b/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml
index fee54f5217f..b25a716bf36 100644
--- a/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml
+++ b/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/mcp-descriptors/mcp-descriptor-support.yaml
@@ -66,7 +66,7 @@ prompts:
         - "Ask the user to choose an object when search results remain ambiguous after metadata lookup."
   - name: safe_sql_execution
     title: Safe SQL Execution
-    description: "Guide the model to choose database_gateway_execute_query for classifier-approved query SQL and database_gateway_execute_update preview before side effects."
+    description: "Guide the model to choose database_gateway_execute_query for parser-approved query SQL and database_gateway_execute_update preview before side effects."
     binding:
       templateResource: META-INF/shardingsphere-mcp/prompts/safe-sql-execution.md
     arguments:
@@ -93,7 +93,7 @@ prompts:
         - "Stop after database_gateway_execute_update preview unless execution is still intended."
       org.apache.shardingsphere/ask-user-conditions:
         - "Ask before database_gateway_execute_update execution when the previewed side effects are ambiguous."
-        - "Ask when the SQL intent cannot be classified as classifier-approved query SQL or side-effecting SQL."
+        - "Ask when the SQL intent cannot be classified as parser-approved query SQL or side-effecting SQL."
   - name: recover_workflow
     title: Recover Workflow
     description: "Guide the model to recover a failed or stale ShardingSphere workflow by checking the current-session plan_id and re-planning when the plan is unavailable."
diff --git a/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/prompts/safe-sql-execution.md b/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/prompts/safe-sql-execution.md
index 08e4f6b6381..959aab926b4 100644
--- a/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/prompts/safe-sql-execution.md
+++ b/mcp/support/src/main/resources/META-INF/shardingsphere-mcp/prompts/safe-sql-execution.md
@@ -24,17 +24,17 @@ User context:
 
 Model path:
 1. Read shardingsphere://databases/{{database}}/capabilities before execution when statement support is uncertain.
-2. Use database_gateway_execute_query only for one classifier-approved SELECT statement.
+2. Use database_gateway_execute_query only for one parser-approved SELECT statement.
 3. Use database_gateway_execute_explain_query for execution-plan diagnostics; pass the original SELECT as sql and a database-native EXPLAIN as explain_sql. Do not use EXPLAIN ANALYZE.
 4. Use database_gateway_execute_update with execution_mode=preview for DML, DDL, DCL, transaction control, or savepoint statements before execution.
-   Treat preview as classification-only, not as a database dry run.
+   Treat preview as database-aware validation and classification, not as a database dry run.
 5. After reviewing the preview, call database_gateway_execute_update with execution_mode=execute and the reviewed SQL only when execution is still intended.
 6. Never split or batch multiple SQL statements into one MCP call.
 7. Before choosing uncertain database, schema, table, or column names, use completion/complete or read the nearest MCP resource; do not guess identifiers.
 
 Ask-user conditions:
 - Ask before database_gateway_execute_update execution when the previewed side effects are ambiguous.
-- Ask when the SQL intent cannot be classified as classifier-approved query SQL or side-effecting SQL.
+- Ask when the SQL intent cannot be classified as parser-approved query SQL or side-effecting SQL.
 
 Stop conditions:
 - Stop after database_gateway_execute_query returns the requested query result.
diff --git a/mode/type/standalone/repository/provider/jdbc/src/main/java/org/apache/shardingsphere/mode/repository/standalone/jdbc/JDBCRepository.java b/mode/type/standalone/repository/provider/jdbc/src/main/java/org/apache/shardingsphere/mode/repository/standalone/jdbc/JDBCRepository.java
index 1591973c87b..b8e1260bf84 100644
--- a/mode/type/standalone/repository/provider/jdbc/src/main/java/org/apache/shardingsphere/mode/repository/standalone/jdbc/JDBCRepository.java
+++ b/mode/type/standalone/repository/provider/jdbc/src/main/java/org/apache/shardingsphere/mode/repository/standalone/jdbc/JDBCRepository.java
@@ -201,13 +201,6 @@ public final class JDBCRepository implements StandalonePersistRepository {
         }
     }
     
-    /**
-     * Delete the specified row.
-     * Once the database connection involved in this row of data has been closed by other threads and this row of data is located in the H2Database started in memory mode,
-     * the data is actually deleted.
-     *
-     * @param key key of data
-     */
     @Override
     public void delete(final String key) {
         if (dataSource.isClosed() && dataSource.getJdbcUrl().startsWith("jdbc:h2:mem:")) {
diff --git a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/ddl/partition/ModifyPartitionDefinitionSegment.java b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/ddl/partition/ModifyPartitionDefinitionSegment.java
index 298b7bbd2cc..7c8b2bd5916 100644
--- a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/ddl/partition/ModifyPartitionDefinitionSegment.java
+++ b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/ddl/partition/ModifyPartitionDefinitionSegment.java
@@ -42,15 +42,4 @@ public final class ModifyPartitionDefinitionSegment implements AlterDefinitionSe
     private final PropertiesSegment properties;
     
     private final boolean allPartitions;
-    
-    /**
-     * Constructor for single partition.
-     *
-     * @param startIndex start index
-     * @param stopIndex stop index
-     * @param properties properties
-     */
-    public ModifyPartitionDefinitionSegment(final int startIndex, final int stopIndex, final PropertiesSegment properties) {
-        this(startIndex, stopIndex, properties, false);
-    }
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java
index 89d13fab5af..391912c7008 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPConversationRunnerFailureTest.java
@@ -271,22 +271,6 @@ class LLMMCPConversationRunnerFailureTest extends AbstractLLMMCPConversationRunn
         verify(getMCPInteractionClient(), never()).readResource(anyString());
     }
     
-    @Test
-    void assertRunWithUnsafeSqlAttempted() throws IOException, InterruptedException {
-        LLME2EScenario actualScenario = createScenario(List.of("database_gateway_execute_query"));
-        LLMMCPConversationRunner actualRunner = createRunner(1);
-        when(getLLMChatClient().complete(anyList(), anyList(), eq("required"), eq(false))).thenReturn(
-                createToolCallCompletion("tool-1", "database_gateway_execute_query",
-                        Map.of("database", DATABASE_NAME, "schema", SCHEMA_NAME, "sql", "UPDATE orders SET status = 'DONE'"),
-                        "tool-call-response"));
-        
-        LLME2EArtifactBundle actual = actualRunner.run(actualScenario);
-        
-        assertThat(actual.getAssertionReport().getFailureType(), is("unsafe_sql_attempted"));
-        assertThat(actual.getInteractionTrace().getFirst().getTargetName(), is("database_gateway_execute_query"));
-        verify(getMCPInteractionClient(), never()).call(anyString(), anyMap());
-    }
-    
     @Test
     void assertRunWithUnsafeExecuteUpdateAttempted() throws IOException, InterruptedException {
         LLME2EScenario actualScenario = createScenario(List.of("database_gateway_execute_update"));
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidator.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidator.java
index 075c8de58ad..d5b6505f67e 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidator.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidator.java
@@ -17,10 +17,6 @@
 
 package org.apache.shardingsphere.test.e2e.mcp.llm.conversation;
 
-import org.apache.shardingsphere.mcp.api.protocol.exception.MCPInvalidRequestException;
-import org.apache.shardingsphere.mcp.api.protocol.exception.MCPUnsupportedException;
-import org.apache.shardingsphere.mcp.core.tool.handler.execute.StatementClassifier;
-import org.apache.shardingsphere.mcp.support.database.capability.SupportedMCPStatement;
 import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionActionNames;
 
 import java.util.Map;
@@ -33,8 +29,6 @@ final class LLMMCPSafetyValidator {
     
     private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
     
-    private final StatementClassifier statementClassifier = new StatementClassifier();
-    
     Optional<LLMMCPToolCallValidationFailure> validate(final String actionName, final Map<String, Object> arguments) {
         if (MCPInteractionActionNames.READ_RESOURCE.equals(actionName) && Objects.toString(arguments.get("uri"), "").trim().isEmpty()) {
             return Optional.of(new LLMMCPToolCallValidationFailure(MCPInteractionActionNames.RESOURCE_READ_KIND, "invalid_tool_arguments", "Model returned an empty resource URI."));
@@ -46,15 +40,6 @@ final class LLMMCPSafetyValidator {
             return Optional.of(new LLMMCPToolCallValidationFailure("tool_call", "invalid_tool_arguments",
                     "Model routed EXPLAIN SQL to database_gateway_execute_query instead of database_gateway_execute_explain_query."));
         }
-        if ("database_gateway_execute_query".equals(actionName) && !isQuery(arguments)) {
-            return Optional.of(new LLMMCPToolCallValidationFailure("tool_call", "unsafe_sql_attempted", "Model attempted a non-read-only SQL statement."));
-        }
-        if ("database_gateway_execute_explain_query".equals(actionName) && !isQuery(arguments)) {
-            return Optional.of(new LLMMCPToolCallValidationFailure("tool_call", "unsafe_sql_attempted", "Model attempted to explain a non-read-only SQL statement."));
-        }
-        if ("database_gateway_execute_update".equals(actionName) && isQuery(arguments)) {
-            return Optional.of(new LLMMCPToolCallValidationFailure("tool_call", "invalid_tool_arguments", "Model routed a read-only SQL statement to the side-effecting SQL tool."));
-        }
         if ("database_gateway_execute_update".equals(actionName) && !EXECUTION_MODE_PREVIEW.equals(Objects.toString(arguments.get("execution_mode"), ""))) {
             return Optional.of(new LLMMCPToolCallValidationFailure("tool_call", "unsafe_sql_execution_attempted",
                     "Model attempted to execute side-effecting SQL in an LLM usability scenario."));
@@ -66,15 +51,6 @@ final class LLMMCPSafetyValidator {
         return Optional.empty();
     }
     
-    private boolean isQuery(final Map<String, Object> arguments) {
-        String sql = Objects.toString(arguments.get("sql"), "").trim();
-        try {
-            return SupportedMCPStatement.QUERY == statementClassifier.classify(sql).getStatementClass();
-        } catch (final MCPInvalidRequestException | MCPUnsupportedException | IllegalArgumentException ignored) {
-            return false;
-        }
-    }
-    
     private boolean isExplain(final Map<String, Object> arguments) {
         String sql = Objects.toString(arguments.get("sql"), "").trim();
         return "EXPLAIN".equalsIgnoreCase(sql) || 7 < sql.length() && sql.regionMatches(true, 0, "EXPLAIN", 0, 7)
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidatorTest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidatorTest.java
index 8dce412dff1..3b2772b5bee 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidatorTest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/LLMMCPSafetyValidatorTest.java
@@ -31,13 +31,6 @@ class LLMMCPSafetyValidatorTest {
     
     private final LLMMCPSafetyValidator validator = new LLMMCPSafetyValidator();
     
-    @Test
-    void assertAllowReadOnlyCommonTableExpressionQuery() {
-        Optional<LLMMCPToolCallValidationFailure> actual = validator.validate("database_gateway_execute_query",
-                Map.of("sql", "WITH foo_orders AS (SELECT * FROM orders) SELECT * FROM foo_orders"));
-        assertFalse(actual.isPresent());
-    }
-    
     @Test
     void assertRejectExplainFromQueryTool() {
         Optional<LLMMCPToolCallValidationFailure> actual = validator.validate("database_gateway_execute_query",
@@ -53,22 +46,6 @@ class LLMMCPSafetyValidatorTest {
         assertFalse(actual.isPresent());
     }
     
-    @Test
-    void assertRejectMutatingExplainQuery() {
-        Optional<LLMMCPToolCallValidationFailure> actual = validator.validate("database_gateway_execute_explain_query",
-                Map.of("sql", "UPDATE orders SET status = 'PAID'", "explain_sql", "EXPLAIN UPDATE orders SET status = 'PAID'"));
-        assertTrue(actual.isPresent());
-        assertThat(actual.get().failureType(), is("unsafe_sql_attempted"));
-    }
-    
-    @Test
-    void assertRejectDataModifyingCommonTableExpressionQuery() {
-        Optional<LLMMCPToolCallValidationFailure> actual = validator.validate("database_gateway_execute_query",
-                Map.of("sql", "WITH updated_orders AS (UPDATE orders SET status = 'PAID' RETURNING *) SELECT * FROM updated_orders"));
-        assertTrue(actual.isPresent());
-        assertThat(actual.get().failureType(), is("unsafe_sql_attempted"));
-    }
-    
     @Test
     void assertRejectExecuteUpdateExecution() {
         Optional<LLMMCPToolCallValidationFailure> actual = validator.validate("database_gateway_execute_update",