Scert/backend/internal/cli/mysql_stored_definition.go
xuans c4051c9166 保存MySQL例程备份整改检查点(验证未完成)
保留存储过程与函数导出、权限检查、编码处理、共享存储对象逻辑及正式测试,同步两份任务进度文档。

已知阻塞:已有MySQL 5.7实库记录中,两种恢复入口均存在Latin-1例程注释乱码;已有8.0编码与权限专项通过,但不能代替最终双版本全量复测。本次仅重新运行例程单元测试及相关包静态检查,均通过。

这是未完成整改的保存检查点,不是发布版本,不表示P0-05完成或生产验收通过。未纳入运行数据、构建产物、原始实库日志或凭据。
2026-09-27 22:16:23 +08:00

95 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cli
import (
"context"
"database/sql"
"fmt"
"io"
"strings"
)
func readMySQLStoredDefinition(ctx context.Context, tx *sql.Tx, kind, name, statementColumn string) (map[string]string, error) {
rows, err := tx.QueryContext(ctx, "SHOW CREATE "+kind+" "+quoteMySQLIdentifier(name))
if err != nil {
return nil, fmt.Errorf("读取 MySQL 存储对象定义失败(%s %s):%w", kind, name, err)
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
return nil, err
}
values := make([]sql.NullString, len(columns))
pointers := make([]any, len(columns))
for i := range values {
pointers[i] = &values[i]
}
if !rows.Next() {
if err := rows.Err(); err != nil {
return nil, err
}
return nil, fmt.Errorf("MySQL 存储对象定义不可见(%s %s)", kind, name)
}
if err := rows.Scan(pointers...); err != nil {
return nil, err
}
definition := make(map[string]string, len(columns))
for i, column := range columns {
if values[i].Valid {
definition[column] = values[i].String
}
}
for _, column := range []string{"sql_mode", statementColumn, "character_set_client", "collation_connection", "Database Collation"} {
value, exists := definition[column]
if !exists || (value == "" && column != "sql_mode") {
return nil, fmt.Errorf("MySQL 存储对象定义字段不完整(%s %s):%s", kind, name, column)
}
}
if rows.Next() {
return nil, fmt.Errorf("MySQL 存储对象定义返回了多行(%s %s)", kind, name)
}
return definition, rows.Err()
}
func writeMySQLStoredDefinition(ctx context.Context, tx *sql.Tx, output io.Writer, definition map[string]string, column, drop string) error {
charset := quoteMySQLIdentifier(definition["character_set_client"])
encode := func(original string) (string, error) {
var encoded, roundTrip []byte
query := "SELECT CAST(CONVERT(? USING " + charset + ") AS BINARY), CAST(CONVERT(CONVERT(? USING " + charset + ") USING utf8mb4) AS BINARY)"
if err := tx.QueryRowContext(ctx, query, original, original).Scan(&encoded, &roundTrip); err != nil {
return "", fmt.Errorf("转换 MySQL 存储对象字符集失败:%w", err)
}
if string(roundTrip) != original {
return "", fmt.Errorf("MySQL 存储对象无法无损转换到创建字符集")
}
return string(encoded), nil
}
// 删除和创建语句均使用原客户端编码,非 ASCII 标识符不能混用 UTF-8 字节。
encoded, err := encode(definition[column])
if err != nil {
return err
}
encodedDrop := ""
if drop != "" {
encodedDrop, err = encode(drop)
if err != nil {
return err
}
}
delimiter := ";;"
for strings.Contains(encoded, delimiter) || strings.Contains(encodedDrop, delimiter) {
delimiter += ";"
}
if _, err := fmt.Fprintf(output, "ALTER DATABASE COLLATE %s;\nSET SESSION sql_mode = %s;\nSET SESSION character_set_client = %s;\nSET SESSION collation_connection = %s;\nDELIMITER %s\n",
quoteMySQLIdentifier(definition["Database Collation"]), mysqlStringLiteral(definition["sql_mode"]),
mysqlStringLiteral(definition["character_set_client"]), mysqlStringLiteral(definition["collation_connection"]), delimiter); err != nil {
return err
}
if encodedDrop != "" {
if _, err := fmt.Fprintf(output, "%s%s\n", encodedDrop, delimiter); err != nil {
return err
}
}
_, err = fmt.Fprintf(output, "%s%s\nDELIMITER ;\n", encoded, delimiter)
return err
}