{"record":{"id":"009407087d2ab8f6","repo":"apache/shardingsphere","slug":"statement-is-not-a-transaction-command","errorCode":null,"errorMessage":"Statement is not a transaction command.","messagePattern":"Statement is not a transaction command\\.","errorType":"validation","errorClass":"MCPInvalidRequestException","httpStatus":null,"severity":"error","filePath":"mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutor.java","lineNumber":84,"sourceCode":"                    0, 0, classificationResult.getNormalizedSql());\n        } catch (final IllegalArgumentException ex) {\n            throw new MCPInvalidRequestException(ex.getMessage(), ex);\n        } catch (final IllegalStateException ex) {\n            throw new MCPTransactionStateException(ex.getMessage(), ex);\n        }\n    }\n    \n    private void executeSavepointStatement(final String sessionId, final MCPDatabaseCapability databaseCapability, final ClassificationResult classificationResult) {\n        String statementType = classificationResult.getStatementType();\n        String savepointName = classificationResult.getSavepointName().orElse(\"\");\n        if (\"SAVEPOINT\".equals(statementType)) {\n            executeSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName));\n        } else if (\"ROLLBACK TO SAVEPOINT\".equals(statementType)) {\n            executeRollbackSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName));\n        } else if (\"RELEASE SAVEPOINT\".equals(statementType)) {\n            executeReleaseSavepoint(sessionId, databaseCapability, getRequiredSavepointName(savepointName));\n        } else {\n            throw new MCPInvalidRequestException(\"Statement is not a transaction command.\");\n        }\n    }\n    \n    private String getRequiredSavepointName(final String savepointName) {\n        String result = savepointName.trim();\n        ShardingSpherePreconditions.checkNotEmpty(result, () -> new IllegalArgumentException(\"Savepoint name is required.\"));\n        return result;\n    }\n    \n    private void executeBeginTransaction(final String sessionId, final String databaseName, final MCPDatabaseCapability databaseCapability) {\n        ShardingSpherePreconditions.checkState(databaseCapability.supportsTransactionControl(), () -> new MCPUnsupportedException(\"Transaction control is not supported.\"));\n        sessionManager.getTransactionResourceManager().beginTransaction(sessionId, databaseName);\n    }\n    \n    private void executeCommit(final String sessionId, final MCPDatabaseCapability databaseCapability) {\n        ShardingSpherePreconditions.checkState(databaseCapability.supportsTransactionControl(), () -> new MCPUnsupportedException(\"Transaction control is not supported.\"));\n        sessionManager.getTransactionResourceManager().commitTransaction(sessionId);\n    }","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/apache/shardingsphere/blob/e952770a215630a3659c75d64369168cd3e26b82/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/MCPJdbcTransactionStatementExecutor.java#L66-L102","documentation":"Thrown by MCPJdbcTransactionStatementExecutor.executeSavepointStatement when the transaction-statement router cannot classify the statement as SAVEPOINT, ROLLBACK TO SAVEPOINT, or RELEASE SAVEPOINT. It is an MCPInvalidRequestException: the caller asked the transaction endpoint to run something that is not one of the three recognized savepoint commands.","triggerScenarios":"executeSavepointStatement() is entered with a classificationResult whose statementType is none of \"SAVEPOINT\", \"ROLLBACK TO SAVEPOINT\", \"RELEASE SAVEPOINT\" — typically a classifier mismatch, e.g. `RELEASE sp1` without the SAVEPOINT keyword, `ROLLBACK TO sp1`, or a custom statement type string passed directly to the transaction tool.","commonSituations":"Dialect differences: PostgreSQL allows `RELEASE SAVEPOINT sp` and MySQL uses `RELEASE SAVEPOINT` only, so scripts written for one engine send short forms the classifier does not produce; calling the transaction MCP tool with free-text SQL instead of going through SQL classification; version changes that rename statement types.","solutions":["Use the full canonical forms: `SAVEPOINT name`, `ROLLBACK TO SAVEPOINT name`, `RELEASE SAVEPOINT name`","Verify you are calling the transaction tool with classified transaction statements, not raw arbitrary SQL","If the failure persists with canonical SQL, check that the classifier produced the expected statementType and report the mismatch against MCPJdbcTransactionStatementExecutor"],"exampleFix":"-- before\nROLLBACK TO sp1;\nRELEASE sp1;\n\n-- after\nROLLBACK TO SAVEPOINT sp1;\nRELEASE SAVEPOINT sp1;","handlingStrategy":"validation","validationCode":"// Only submit canonical savepoint forms to the transaction tool\nprivate static final Set<String> CANONICAL = Set.of(\"SAVEPOINT\", \"ROLLBACK TO SAVEPOINT\", \"RELEASE SAVEPOINT\");\nif (!CANONICAL.contains(classify(sql))) throw new IllegalArgumentException(\"Not a supported transaction command: \" + sql);","typeGuard":null,"tryCatchPattern":"try {\n    transactionTool.execute(sessionId, sql);\n} catch (MCPInvalidRequestException e) {\n    if (e.getMessage().contains(\"not a transaction command\")) {\n        // send through the normal execute tool instead if it is plain SQL\n    }\n}","preventionTips":["Always write SAVEPOINT commands in full canonical form","Do not send arbitrary SQL to the transaction endpoint"],"tags":["mcp","transaction","savepoint","request-validation","shardingsphere"],"backgroundTag":null,"analyzedSha":"e952770a215630a3659c75d64369168cd3e26b82","analyzedAt":"2026-08-14T13:54:53.392Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}