apache/shardingsphere · error · SQLToolMismatchException

database_gateway_execute_query only supports parser-approved

Error message

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.

What it means

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.

Source

Thrown at mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/tool/handler/execute/ExecuteQueryToolHandler.java:60

    public String getToolName() {
        return CoreToolNames.EXECUTE_QUERY;
    }
    
    @Override
    public MCPSuccessPayload handle(final MCPFeatureRequestContext requestContext, final Map<String, Object> arguments) {
        MCPToolArguments toolArguments = new MCPToolArguments(arguments);
        String sql = toolArguments.getStringArgument("sql");
        checkReadOnlyQuery(requestContext, toolArguments, sql);
        SQLExecutionToolHandlerSupport.checkExecutionArguments(toolArguments, CoreToolNames.EXECUTE_QUERY);
        return SQLExecutionPayload.query(requestContext.getExecutionFacade().execute(SQLExecutionToolHandlerSupport.createReadOnlyExecutionRequest(
                requestContext.getSessionIdentity().getSessionId(), toolArguments,
                SQLExecutionToolHandlerSupport.resolveSchema(requestContext, toolArguments), sql, CoreToolNames.EXECUTE_QUERY)));
    }
    
    private void checkReadOnlyQuery(final MCPFeatureRequestContext requestContext, final MCPToolArguments toolArguments, final String sql) {
        ClassificationResult classificationResult = SQLExecutionToolHandlerSupport.analyze(requestContext, toolArguments, sql);
        if (!SQLExecutionToolHandlerSupport.isQueryStatement(classificationResult)) {
            throw new SQLToolMismatchException(
                    "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.",
                    CoreToolNames.EXECUTE_QUERY, CoreToolNames.EXECUTE_UPDATE, classificationResult,
                    createSuggestedArguments(toolArguments, classificationResult));
        }
    }
    
    private Map<String, Object> createSuggestedArguments(final MCPToolArguments toolArguments, final ClassificationResult classificationResult) {
        Map<String, Object> result = new LinkedHashMap<>(4, 1F);
        SQLExecutionToolHandlerSupport.putIfNotEmpty(result, "database", toolArguments.getStringArgument("database"));
        SQLExecutionToolHandlerSupport.putIfNotEmpty(result, "schema", toolArguments.getStringArgument("schema"));
        result.put("sql", classificationResult.getNormalizedSql());
        result.put(MCPPayloadFieldNames.EXECUTION_MODE, "preview");
        return result;
    }
}

View on GitHub (pinned to e952770a21)

Solutions

  1. Route DML/DDL/DCL to database_gateway_execute_update with an explicit execution_mode.
  2. Route EXPLAIN statements to database_gateway_execute_explain_query.
  3. Use the exception's suggested-arguments payload to reroute programmatically to the recommended tool.
  4. Client-side, pre-classify by leading keyword (SELECT/WITH-only reads -> execute_query) before choosing the tool.

Example fix

// before
await tools.call('database_gateway_execute_query', { sql: "UPDATE t SET a=1" }); // SQLToolMismatchException

// after
const leading = sql.trimStart().toUpperCase();
const tool = leading.startsWith('EXPLAIN') ? 'database_gateway_execute_explain_query'
           : /^(SELECT|WITH)\b/.test(leading) ? 'database_gateway_execute_query'
           : 'database_gateway_execute_update';
await tools.call(tool, leading.startsWith('EXPLAIN') ? { sql } : { sql, execution_mode: 'execute' });
Defensive patterns

Strategy: validation

Validate before calling

// Route by statement shape before choosing the tool
function pickSqlTool(sql) {
  const s = sql.trim().replace(/\/\*.*?\*\//g, '').trim();
  const upper = s.toUpperCase();
  if (/^(EXPLAIN|DESC|DESCRIBE)\b/.test(upper)) return ['database_gateway_execute_explain_query', {}];
  if (/^WITH\b/.test(upper)) return null; // CTE: could be read or write - try query, reroute on mismatch
  if (/^SELECT\b/.test(upper)) return ['database_gateway_execute_query', {}];
  return ['database_gateway_execute_update', { execution_mode: 'execute' }];
}
const [tool, extra] = pickSqlTool(sql);

Type guard

function looksLikeQuery(sql) {
  const upper = sql.trim().replace(/^[;(\s]+/, '').toUpperCase();
  return /^SELECT\b/.test(upper) || (/^WITH\b/.test(upper) && !/\b(INERT|INSERT|UPDATE|DELETE|MERGE)\b/.test(upper));
}

Try / catch

try {
  return await tools.call('database_gateway_execute_query', { sql });
} catch (e) {
  if (e.name === 'SQLToolMismatchException' && e.suggestedArguments) {
    return tools.call(e.suggestedTool, e.suggestedArguments); // reroute via exception payload
  }
  throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14). Data as JSON: /api/errors/904fef520abe9156. Report an issue: GitHub.