OtterMind/Chat2DB · error · BusinessException

routine.operation.ddlRequired

routine.operation.ddlRequired

Error message

routine.operation.ddlRequired

What it means

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.

Source

Thrown at chat2db-community-server/chat2db-community-plugins/chat2db-community-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlRoutineManager.java:352

        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;
    }

    private String ensureSqlEndsWithSemicolon(String sql) {
        String trimmed = StringUtils.trimToEmpty(sql);
        if (StringUtils.isBlank(trimmed)) {
            throw new BusinessException("routine.operation.ddlRequired");
        }
        return trimmed.endsWith(";") ? trimmed : trimmed + ";";
    }

    private String mysqlQualifiedName(String databaseName, String routineName) {
        return Arrays.asList(databaseName, routineName).stream()
                .filter(StringUtils::isNotBlank)
                .map(this::quoteMysqlIdentifier)
                .reduce((left, right) -> left + "." + right)
                .orElseThrow(() -> new BusinessException("routine.operation.nameRequired"));
    }

    private String quoteMysqlIdentifier(String name) {
        return MysqlIdentifierProcessor.INSTANCE.quoteIdentifierAlways(name);
    }

    private String routineInvocationName(String name) {
        String trimmed = StringUtils.trimToEmpty(name);

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Ensure operation.setDdl(createStatement) carries a non-empty CREATE FUNCTION/PROCEDURE body.
  2. Validate the DDL is non-blank and starts with CREATE at the request boundary.
  3. If migrating from an existing routine, capture the DDL via SHOW CREATE before submitting.
  4. Reject empty DDL in the editor with an inline validation message before submit.

Example fix

// before
operation.setDdl("");
routineManager.executeMigration(connection, operation);

// after
if (StringUtils.isBlank(operation.getDdl())) {
    throw new IllegalArgumentException("DDL body is required for migration");
}
operation.setDdl(operation.getDdl());
routineManager.executeMigration(connection, operation);
Defensive patterns

Strategy: validation

Validate before calling

if (StringUtils.isBlank(operation.getDdl())) {
    throw new IllegalArgumentException("DDL body is required for migration");
}

Type guard

static boolean hasDdl(RoutineOperation op) {
    return op != null && StringUtils.isNotBlank(op.getDdl());
}

Prevention

When it happens

Trigger: Calling previewMigration/executeMigration (via buildMigrationPlan → ensureSqlEndsWithSemicolon) with operation.getDdl() returning null, empty, or whitespace-only.

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

Related errors


AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14). Data as JSON: /api/errors/de21bfc41a3b4d27. Report an issue: GitHub.