保留原始视图定义与编码,按依赖排序导出,并严格识别和重排官方最终视图块;增加运行上下文和权限检查。补充视图、清单、恢复失败保护测试,并修正旧事件、触发器和例程测试的权限前提。 隔离MySQL 5.7和8.0完整专项分别通过176和202个叶子用例,无失败或跳过;后端常规全量、静态检查、模块校验及Linux测试包交叉编译通过。测试产物和凭据未提交。本提交不代表生产部署验收完成。
78 lines
2.9 KiB
Go
78 lines
2.9 KiB
Go
package cli
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
)
|
||
|
||
type mysqlView struct {
|
||
name string
|
||
definition map[string]string
|
||
}
|
||
|
||
func dumpMySQLViews(ctx context.Context, tx *sql.Tx, output io.Writer, names []string) (result error) {
|
||
if len(names) == 0 {
|
||
return nil
|
||
}
|
||
var schema, databaseCollation, sourceMode string
|
||
var lowerCaseNames, sourceQuote int
|
||
if err := tx.QueryRowContext(ctx, "SELECT DATABASE(), @@collation_database, @@SESSION.sql_mode, @@lower_case_table_names, @@SESSION.sql_quote_show_create").Scan(&schema, &databaseCollation, &sourceMode, &lowerCaseNames, &sourceQuote); err != nil {
|
||
return err
|
||
}
|
||
// 仅解析服务端在固定模式下输出的依赖;创建语句保持原样,不重写用户 SQL。
|
||
if _, err := tx.ExecContext(ctx, "SET SESSION sql_mode = '', SESSION sql_quote_show_create = 1"); err != nil {
|
||
return err
|
||
}
|
||
defer func() {
|
||
_, err := tx.ExecContext(ctx, "SET SESSION sql_mode = ?, SESSION sql_quote_show_create = ?", sourceMode, sourceQuote)
|
||
result = errors.Join(result, err)
|
||
}()
|
||
views := make([]mysqlView, 0, len(names))
|
||
for _, name := range names {
|
||
definition, err := readMySQLStoredDefinition(ctx, tx, "VIEW", name, "Create View")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
definition["sql_mode"] = ""
|
||
definition["Database Collation"] = databaseCollation
|
||
var columns int
|
||
if err := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?", name).Scan(&columns); err != nil {
|
||
return fmt.Errorf("读取 MySQL 视图列失败(%s):%w", name, err)
|
||
}
|
||
if columns == 0 {
|
||
return fmt.Errorf("MySQL 视图列不可见或已失效,拒绝生成不完整备份:%s", name)
|
||
}
|
||
views = append(views, mysqlView{name: name, definition: definition})
|
||
}
|
||
views, err := orderMySQLViews(views, schema, lowerCaseNames)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if _, err := fmt.Fprintln(output, `SET @scert_view_sql_mode = @@SESSION.sql_mode;
|
||
SET @scert_view_charset = @@SESSION.character_set_client;
|
||
SET @scert_view_collation = @@SESSION.collation_connection;
|
||
SET SESSION sql_mode = '';
|
||
SET SESSION character_set_client = 'utf8mb4';
|
||
SET SESSION collation_connection = 'utf8mb4_bin';`); err != nil {
|
||
return err
|
||
}
|
||
// 先按反向依赖删除视图,再正向创建;不创建占位表,也绝不删除同名基础表。
|
||
for i := len(views) - 1; i >= 0; i-- {
|
||
if _, err := fmt.Fprintf(output, "DROP VIEW IF EXISTS %s;\n", quoteMySQLIdentifier(views[i].name)); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
for _, view := range views {
|
||
if err := writeMySQLStoredDefinition(ctx, tx, output, view.definition, "Create View", ""); err != nil {
|
||
return fmt.Errorf("写入 MySQL 视图失败(%s):%w", view.name, err)
|
||
}
|
||
}
|
||
_, err = fmt.Fprintln(output, `SET SESSION sql_mode = @scert_view_sql_mode;
|
||
SET SESSION character_set_client = @scert_view_charset;
|
||
SET SESSION collation_connection = @scert_view_collation;`)
|
||
return err
|
||
}
|