{"record":{"id":"de21bfc41a3b4d27","repo":"OtterMind/Chat2DB","slug":"routine-operation-ddlrequired","errorCode":"routine.operation.ddlRequired","errorMessage":"routine.operation.ddlRequired","messagePattern":"routine\\.operation\\.ddlRequired","errorType":"validation","errorClass":"BusinessException","httpStatus":null,"severity":"error","filePath":"chat2db-community-server/chat2db-community-plugins/chat2db-community-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlRoutineManager.java","lineNumber":352,"sourceCode":"        String routineName = StringUtils.trimToEmpty(operation.getRoutineName());\n        if (StringUtils.isBlank(routineName)) {\n            throw new BusinessException(\"routine.operation.nameRequired\");\n        }\n        return routineName;\n    }\n\n    private String requireDatabaseName(RoutineOperation operation) {\n        String databaseName = StringUtils.trimToEmpty(operation.getDatabaseName());\n        if (StringUtils.isBlank(databaseName)) {\n            throw new BusinessException(\"routine.operation.databaseRequired\");\n        }\n        return databaseName;\n    }\n\n    private String ensureSqlEndsWithSemicolon(String sql) {\n        String trimmed = StringUtils.trimToEmpty(sql);\n        if (StringUtils.isBlank(trimmed)) {\n            throw new BusinessException(\"routine.operation.ddlRequired\");\n        }\n        return trimmed.endsWith(\";\") ? trimmed : trimmed + \";\";\n    }\n\n    private String mysqlQualifiedName(String databaseName, String routineName) {\n        return Arrays.asList(databaseName, routineName).stream()\n                .filter(StringUtils::isNotBlank)\n                .map(this::quoteMysqlIdentifier)\n                .reduce((left, right) -> left + \".\" + right)\n                .orElseThrow(() -> new BusinessException(\"routine.operation.nameRequired\"));\n    }\n\n    private String quoteMysqlIdentifier(String name) {\n        return MysqlIdentifierProcessor.INSTANCE.quoteIdentifierAlways(name);\n    }\n\n    private String routineInvocationName(String name) {\n        String trimmed = StringUtils.trimToEmpty(name);","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/chat2db-community-server/chat2db-community-plugins/chat2db-community-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlRoutineManager.java#L334-L370","documentation":"Thrown by ensureSqlEndsWithSemicolon when operation.getDdl(), after trim, is blank. During migration the new routine body (CREATE statement) is mandatory: the manager executes DROP then the provided CREATE, so an empty CREATE would leave no routine behind. The guard rejects this before building the migration plan.","triggerScenarios":"Calling previewMigration/executeMigration (via buildMigrationPlan → ensureSqlEndsWithSemicolon) with operation.getDdl() returning null, empty, or whitespace-only.","commonSituations":"A migration submitted from a UI where the DDL editor was left empty; a programmatic caller that builds the operation but forgets to attach the CREATE body; a paste/parse step that dropped the DDL content.","solutions":["Ensure operation.setDdl(createStatement) carries a non-empty CREATE FUNCTION/PROCEDURE body.","Validate the DDL is non-blank and starts with CREATE at the request boundary.","If migrating from an existing routine, capture the DDL via SHOW CREATE before submitting.","Reject empty DDL in the editor with an inline validation message before submit."],"exampleFix":"// before\noperation.setDdl(\"\");\nroutineManager.executeMigration(connection, operation);\n\n// after\nif (StringUtils.isBlank(operation.getDdl())) {\n    throw new IllegalArgumentException(\"DDL body is required for migration\");\n}\noperation.setDdl(operation.getDdl());\nroutineManager.executeMigration(connection, operation);","handlingStrategy":"validation","validationCode":"if (StringUtils.isBlank(operation.getDdl())) {\n    throw new IllegalArgumentException(\"DDL body is required for migration\");\n}","typeGuard":"static boolean hasDdl(RoutineOperation op) {\n    return op != null && StringUtils.isNotBlank(op.getDdl());\n}","tryCatchPattern":null,"preventionTips":["Require a non-empty CREATE body in the migration editor before submit.","Capture the DDL via SHOW CREATE when migrating an existing routine.","Validate the DDL starts with CREATE at the boundary.","Reject empty DDL with an inline message rather than reaching the manager."],"tags":["mysql","routine","migration","validation"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}