OtterMind/Chat2DB · error · BusinessException
routine.operation.typeUnsupported
routine.operation.typeUnsupported
Error message
routine.operation.typeUnsupported
What it means
Thrown by normalizeRoutineType when operation.getRoutineType(), after trim+uppercase, is neither FUNCTION nor PROCEDURE. This is a hard validation: only those two routine kinds are supported by the MySQL routine manager. Any other value (including null, empty, 'TRIGGER', 'EVENT') is rejected before any SQL is built.
Solutions
- Set operation.setRoutineType("FUNCTION") or "PROCEDURE" (case-insensitive) before calling.
- Validate the routineType at the request boundary and reject unsupported kinds with a clear message.
- If the value comes from metadata, map it to FUNCTION/PROCEDURE and skip other kinds.
- Confirm the RoutineOperation builder is not leaving routineType unset.
Example fix
// before
operation.setRoutineType(rawType);
routineManager.previewInvocation(connection, operation);
// after
String type = StringUtils.trimToEmpty(rawType).toUpperCase(Locale.ROOT);
if (!"FUNCTION".equals(type) && !"PROCEDURE".equals(type)) {
throw new IllegalArgumentException("Unsupported routine type: " + rawType);
}
operation.setRoutineType(type);
routineManager.previewInvocation(connection, operation); Defensive patterns
Strategy: validation
Validate before calling
String t = StringUtils.trimToEmpty(rawType).toUpperCase(Locale.ROOT);
if (!"FUNCTION".equals(t) && !"PROCEDURE".equals(t)) {
throw new IllegalArgumentException("Unsupported routine type: " + rawType);
}
operation.setRoutineType(t); Type guard
static boolean isSupportedRoutineType(String type) {
String t = StringUtils.trimToEmpty(type).toUpperCase(Locale.ROOT);
return "FUNCTION".equals(t) || "PROCEDURE".equals(t);
} Prevention
- Restrict the routine-type picker to FUNCTION/PROCEDURE.
- Map metadata routine types to FUNCTION/PROCEDURE at the boundary.
- Reject unsupported kinds early with a clear validation message.
- Never leave routineType unset on a RoutineOperation.
When it happens
Trigger: Calling any routine-manager entry point (previewInvocation, previewMigration, executeMigration) with operation.getRoutineType() returning a value other than FUNCTION/PROCEDURE, case-insensitively.
Common situations: Frontend sends the routine type from a dropdown that includes unsupported kinds; a null/blank routineType field in the request payload; a copy of the operation object left with a default/null routineType; mislabeling an EVENT or TRIGGER as a routine.
Related errors
- routine.operation.databaseRequired
- routine.operation.ddlRequired
- routine.operation.nameRequired
- Existing routine definition does not contain routine name
- Existing routine definition is empty
AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14).
Data as JSON: /api/errors/928753bf30d59612.
Report an issue: GitHub.
Appendix: source
Thrown at chat2db-community-server/chat2db-community-plugins/chat2db-community-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlRoutineManager.java:328
.toList();
}
return metaData.getProcedureParameters(connection, databaseName, StringUtils.trimToNull(operation.getSchemaName()),
routineName)
.stream()
.map(mysqlRoutineConverter::procedureParameter2routineParameter)
.filter(Objects::nonNull)
.sorted((left, right) -> Integer.compare(left.ordinalPosition(), right.ordinalPosition()))
.toList();
} catch (Exception e) {
throw new BusinessException("routine.operation.parameterLoadFailed", new Object[]{e.getMessage()}, e);
}
}
private String normalizeRoutineType(String routineType) {
String normalized = StringUtils.trimToEmpty(routineType).toUpperCase(Locale.ROOT);
if (!FUNCTION.equals(normalized) && !PROCEDURE.equals(normalized)) {
throw new BusinessException("routine.operation.typeUnsupported");
}
return normalized;
}
private String requireRoutineName(RoutineOperation operation) {
String routineName = StringUtils.trimToEmpty(operation.getRoutineName());
if (StringUtils.isBlank(routineName)) {
throw new BusinessException("routine.operation.nameRequired");
}
return routineName;
}
private String requireDatabaseName(RoutineOperation operation) {
String databaseName = StringUtils.trimToEmpty(operation.getDatabaseName());
if (StringUtils.isBlank(databaseName)) {
throw new BusinessException("routine.operation.databaseRequired");
}
return databaseName;View on GitHub (pinned to 5ee1e990e7)