package cli import ( "context" "database/sql" "fmt" "strings" "unicode/utf8" ) // MySQL 8.0 将 UTF-8 目录中的声明头和注释与原客户端编码的正文拼接, // 再按客户端字符集返回 SHOW CREATE;非 UTF-8 例程必须分段还原,不能整体反解正文。 func normalizeMySQLRoutineMetadata(ctx context.Context, tx *sql.Tx, routine mysqlRoutine, definition map[string]string, column string) error { charset := definition["character_set_client"] if charset == "utf8" || charset == "utf8mb3" || charset == "utf8mb4" { return nil } original := definition[column] end, err := mysqlRoutineHeaderEnd(original) if err != nil { return err } decode := func(value string) (string, error) { var encoded []byte query := "SELECT CAST(CONVERT(? USING " + quoteMySQLIdentifier(charset) + ") AS BINARY)" if err := tx.QueryRowContext(ctx, query, value).Scan(&encoded); err != nil { return "", err } if !utf8.Valid(encoded) { return "", fmt.Errorf("MySQL 例程目录元数据无法无损还原为 UTF-8:%s", routine.name) } return string(encoded), nil } header, err := decode(original[:end]) if err != nil { return err } var comment string var body sql.NullString if err := tx.QueryRowContext(ctx, `SELECT ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = DATABASE() AND ROUTINE_NAME = ? AND ROUTINE_TYPE = ?`, routine.name, routine.kind).Scan(&comment, &body); err != nil { return fmt.Errorf("读取 MySQL 例程编码元数据失败:%w", err) } rest := original[end:] if comment != "" { if !body.Valid || body.String == "" || !strings.HasSuffix(rest, body.String) { return fmt.Errorf("MySQL 例程正文与目录元数据不一致,拒绝猜测编码:%s", routine.name) } metadata := rest[:len(rest)-len(body.String)] // 注释位于声明选项的末尾;先隔离正文,避免误改业务 SQL 中的 COMMENT 文本。 start := strings.LastIndex(metadata, "\n COMMENT ") if start < 0 { return fmt.Errorf("MySQL 例程注释声明不完整:%s", routine.name) } decoded, err := decode(metadata[start:]) if err != nil { return err } rest = metadata[:start] + decoded + body.String } definition[column] = header + rest return nil } func mysqlRoutineHeaderEnd(statement string) (int, error) { var quote byte for i := 0; i < len(statement); i++ { current := statement[i] if quote != 0 { if current == quote { if i+1 < len(statement) && statement[i+1] == quote { i++ } else { quote = 0 } } continue } if current == '`' || current == '"' || current == '\'' { quote = current } else if current == '(' { return i, nil } } return 0, fmt.Errorf("MySQL 例程声明缺少完整参数列表") }