保留原始视图定义与编码,按依赖排序导出,并严格识别和重排官方最终视图块;增加运行上下文和权限检查。补充视图、清单、恢复失败保护测试,并修正旧事件、触发器和例程测试的权限前提。 隔离MySQL 5.7和8.0完整专项分别通过176和202个叶子用例,无失败或跳过;后端常规全量、静态检查、模块校验及Linux测试包交叉编译通过。测试产物和凭据未提交。本提交不代表生产部署验收完成。
99 lines
3.4 KiB
Go
99 lines
3.4 KiB
Go
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
|
||
}
|
||
}
|
||
required := []string{"sql_mode", statementColumn, "character_set_client", "collation_connection", "Database Collation"}
|
||
if kind == "VIEW" {
|
||
required = []string{statementColumn, "character_set_client", "collation_connection"}
|
||
}
|
||
for _, column := range required {
|
||
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
|
||
}
|