{"record":{"id":"904fef520abe9156","repo":"apache/shardingsphere","slug":"database-gateway-execute-query-only-supports-parse","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"validation","errorClass":"SQLToolMismatchException","httpStatus":null,"severity":"error","filePath":"mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandler.java","lineNumber":60,"sourceCode":"    public String getToolName() {\n        return CoreToolNames.EXECUTE_QUERY;\n    }\n    \n    @Override\n    public MCPSuccessPayload handle(final MCPFeatureRequestContext requestContext, final Map<String, Object> arguments) {\n        MCPToolArguments toolArguments = new MCPToolArguments(arguments);\n        String sql = toolArguments.getStringArgument(\"sql\");\n        checkReadOnlyQuery(requestContext, toolArguments, sql);\n        SQLExecutionToolHandlerSupport.checkExecutionArguments(toolArguments, CoreToolNames.EXECUTE_QUERY);\n        return SQLExecutionPayload.query(requestContext.getExecutionFacade().execute(SQLExecutionToolHandlerSupport.createReadOnlyExecutionRequest(\n                requestContext.getSessionIdentity().getSessionId(), toolArguments,\n                SQLExecutionToolHandlerSupport.resolveSchema(requestContext, toolArguments), sql, CoreToolNames.EXECUTE_QUERY)));\n    }\n    \n    private void checkReadOnlyQuery(final MCPFeatureRequestContext requestContext, final MCPToolArguments toolArguments, final String sql) {\n        ClassificationResult classificationResult = SQLExecutionToolHandlerSupport.analyze(requestContext, toolArguments, sql);\n        if (!SQLExecutionToolHandlerSupport.isQueryStatement(classificationResult)) {\n            throw new SQLToolMismatchException(\n                    \"database_gateway_execute_query only supports parser-approved QUERY statements. \"\n                            + \"Use database_gateway_execute_explain_query for EXPLAIN diagnostics or database_gateway_execute_update for side-effecting SQL.\",\n                    CoreToolNames.EXECUTE_QUERY, CoreToolNames.EXECUTE_UPDATE, classificationResult,\n                    createSuggestedArguments(toolArguments, classificationResult));\n        }\n    }\n    \n    private Map<String, Object> createSuggestedArguments(final MCPToolArguments toolArguments, final ClassificationResult classificationResult) {\n        Map<String, Object> result = new LinkedHashMap<>(4, 1F);\n        SQLExecutionToolHandlerSupport.putIfNotEmpty(result, \"database\", toolArguments.getStringArgument(\"database\"));\n        SQLExecutionToolHandlerSupport.putIfNotEmpty(result, \"schema\", toolArguments.getStringArgument(\"schema\"));\n        result.put(\"sql\", classificationResult.getNormalizedSql());\n        result.put(MCPPayloadFieldNames.EXECUTION_MODE, \"preview\");\n        return result;\n    }\n}\n","sourceCodeStart":42,"sourceCodeEnd":77,"githubUrl":"https://github.com/apache/shardingsphere/blob/e952770a215630a3659c75d64369168cd3e26b82/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandler.java#L42-L77","documentation":"database_gateway_execute_query only accepts statements the analyzer classifies as QUERY; checkReadOnlyQuery analyzes the SQL and throws SQLToolMismatchException when the classification is not a query statement. The message directs the caller to database_gateway_execute_explain_query for EXPLAIN or database_gateway_execute_update for side-effecting SQL, and the exception carries suggested arguments (normalized SQL, database, schema) so a client can automatically reroute to the right tool.","triggerScenarios":"Calling database_gateway_execute_query with INSERT/UPDATE/DELETE/DDL, EXPLAIN ..., or any statement whose leading keyword/parse tree is not a SELECT-class query (including CTE-led writes, which parse as DML despite starting with WITH).","commonSituations":"LLM agent choosing the query tool for a mutation; EXPLAIN ANALYZE sent to the read-only tool; copying SQL between execute_update and execute_query handlers; WITH ... DELETE misclassified by the caller as a read.","solutions":["Route DML/DDL/DCL to database_gateway_execute_update with an explicit execution_mode.","Route EXPLAIN statements to database_gateway_execute_explain_query.","Use the exception's suggested-arguments payload to reroute programmatically to the recommended tool.","Client-side, pre-classify by leading keyword (SELECT/WITH-only reads -> execute_query) before choosing the tool."],"exampleFix":"// before\nawait tools.call('database_gateway_execute_query', { sql: \"UPDATE t SET a=1\" }); // SQLToolMismatchException\n\n// after\nconst leading = sql.trimStart().toUpperCase();\nconst tool = leading.startsWith('EXPLAIN') ? 'database_gateway_execute_explain_query'\n           : /^(SELECT|WITH)\\b/.test(leading) ? 'database_gateway_execute_query'\n           : 'database_gateway_execute_update';\nawait tools.call(tool, leading.startsWith('EXPLAIN') ? { sql } : { sql, execution_mode: 'execute' });","handlingStrategy":"validation","validationCode":"// Route by statement shape before choosing the tool\nfunction pickSqlTool(sql) {\n  const s = sql.trim().replace(/\\/\\*.*?\\*\\//g, '').trim();\n  const upper = s.toUpperCase();\n  if (/^(EXPLAIN|DESC|DESCRIBE)\\b/.test(upper)) return ['database_gateway_execute_explain_query', {}];\n  if (/^WITH\\b/.test(upper)) return null; // CTE: could be read or write - try query, reroute on mismatch\n  if (/^SELECT\\b/.test(upper)) return ['database_gateway_execute_query', {}];\n  return ['database_gateway_execute_update', { execution_mode: 'execute' }];\n}\nconst [tool, extra] = pickSqlTool(sql);","typeGuard":"function looksLikeQuery(sql) {\n  const upper = sql.trim().replace(/^[;(\\s]+/, '').toUpperCase();\n  return /^SELECT\\b/.test(upper) || (/^WITH\\b/.test(upper) && !/\\b(INERT|INSERT|UPDATE|DELETE|MERGE)\\b/.test(upper));\n}","tryCatchPattern":"try {\n  return await tools.call('database_gateway_execute_query', { sql });\n} catch (e) {\n  if (e.name === 'SQLToolMismatchException' && e.suggestedArguments) {\n    return tools.call(e.suggestedTool, e.suggestedArguments); // reroute via exception payload\n  }\n  throw e;\n}","preventionTips":["Classify SQL by leading keyword before selecting execute_query vs execute_update vs explain.","Remember WITH-led statements may be writes; let the mismatch exception's suggested arguments reroute.","Never send EXPLAIN to execute_query.","Keep one SQL statement per call; strip leading transactions/semicolons."],"tags":["mcp","sql","tool-routing","classification","validation"],"backgroundTag":null,"analyzedSha":"e952770a215630a3659c75d64369168cd3e26b82","analyzedAt":"2026-08-14T13:54:53.392Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}