Scert/backend/internal/cli/mysql_views.go
xuans 777c7169b6 完善MySQL视图备份恢复及完整清单权限校验
保留原始视图定义与编码,按依赖排序导出,并严格识别和重排官方最终视图块;增加运行上下文和权限检查。补充视图、清单、恢复失败保护测试,并修正旧事件、触发器和例程测试的权限前提。

隔离MySQL 5.7和8.0完整专项分别通过176和202个叶子用例,无失败或跳过;后端常规全量、静态检查、模块校验及Linux测试包交叉编译通过。测试产物和凭据未提交。本提交不代表生产部署验收完成。
2026-09-28 00:35:26 +08:00

78 lines
2.9 KiB
Go
Raw Permalink 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"
"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
}