保留原始视图定义与编码,按依赖排序导出,并严格识别和重排官方最终视图块;增加运行上下文和权限检查。补充视图、清单、恢复失败保护测试,并修正旧事件、触发器和例程测试的权限前提。 隔离MySQL 5.7和8.0完整专项分别通过176和202个叶子用例,无失败或跳过;后端常规全量、静态检查、模块校验及Linux测试包交叉编译通过。测试产物和凭据未提交。本提交不代表生产部署验收完成。
88 lines
3.3 KiB
Go
88 lines
3.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func mysqlViewRestoreFixture(name, query string) []string {
|
|
plain := []string{
|
|
"DROP VIEW IF EXISTS " + quoteMySQLIdentifier(name),
|
|
"SET @saved_cs_client = @@character_set_client",
|
|
"SET @saved_cs_results = @@character_set_results",
|
|
"SET @saved_col_connection = @@collation_connection",
|
|
"SET character_set_client = utf8mb4",
|
|
"SET character_set_results = utf8mb4",
|
|
"SET collation_connection = utf8mb4_bin",
|
|
"CREATE ALGORITHM=MERGE SQL SECURITY INVOKER VIEW " + quoteMySQLIdentifier(name) + " AS " + query,
|
|
"SET character_set_client = @saved_cs_client",
|
|
"SET character_set_results = @saved_cs_results",
|
|
"SET collation_connection = @saved_col_connection",
|
|
}
|
|
for i := range plain {
|
|
plain[i] = "/*!50001 " + plain[i] + " */"
|
|
}
|
|
return plain
|
|
}
|
|
|
|
func TestMySQLViewRestoreBlock(t *testing.T) {
|
|
statements := mysqlViewRestoreFixture("a", "select * from z")
|
|
block, ok := readMySQLViewRestoreBlock(statements)
|
|
if !ok || block.name != "a" || block.charset != "utf8mb4" || !slices.Equal(block.statements, statements) {
|
|
t.Fatal("完整官方视图块未被原样识别")
|
|
}
|
|
for _, test := range []struct {
|
|
name string
|
|
at int
|
|
sql string
|
|
}{
|
|
{"missing_restore", 10, "SELECT 1"},
|
|
{"business_statement", 5, "/*!50001 DELETE FROM user_data */"},
|
|
{"global_setting", 4, "/*!50001 SET GLOBAL character_set_client = utf8mb4 */"},
|
|
{"setting_expression", 4, "/*!50001 SET character_set_client = CONCAT('utf8','mb4') */"},
|
|
{"unknown_restore", 8, "/*!50001 SET character_set_client = @another */"},
|
|
{"extra_assignment", 8, "/*!50001 SET character_set_client = @saved_cs_client, sql_mode = '' */"},
|
|
{"split_identifier", 8, "/*!50001 SET character_set_ client = @saved_cs_client */"},
|
|
{"plain_create", 7, "CREATE VIEW `a` AS SELECT 1"},
|
|
{"future_version", 7, "/*!99999 CREATE ALGORITHM=MERGE VIEW `a` AS SELECT 1 */"},
|
|
{"qualified_drop", 0, "/*!50001 DROP VIEW IF EXISTS `db`.`a` */"},
|
|
{"multiple_drops", 0, "/*!50001 DROP VIEW IF EXISTS `a`, `b` */"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
changed := slices.Clone(statements)
|
|
changed[test.at] = test.sql
|
|
if _, ok := readMySQLViewRestoreBlock(changed); ok {
|
|
t.Fatal("非闭合官方视图块不能被重排")
|
|
}
|
|
})
|
|
}
|
|
for length := 0; length < 11; length++ {
|
|
t.Run(fmt.Sprintf("truncated_%d", length), func(t *testing.T) {
|
|
if _, ok := readMySQLViewRestoreBlock(statements[:length]); ok {
|
|
t.Fatal("截断视图块不能被重排")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMySQLViewDumpWrapper(t *testing.T) {
|
|
for _, source := range []string{
|
|
"/*!50001 CREATE ALGORITHM=MERGE */\n/*!50013 DEFINER=`a`@`b` SQL SECURITY INVOKER */\n/*!50001 VIEW `a` AS SELECT 1 */",
|
|
"/*!50001 CREATE ALGORITHM=MERGE VIEW `a` AS SELECT 1 */ /*!50002 WITH LOCAL CHECK OPTION */",
|
|
} {
|
|
plain, ok := unwrapMySQLViewDumpStatement(source)
|
|
if !ok {
|
|
t.Fatal("官方版本化定义无法展开")
|
|
}
|
|
if name, err := mysqlViewCreateName(plain); err != nil || name != "a" {
|
|
t.Fatalf("视图名称识别失败:%v", err)
|
|
}
|
|
}
|
|
for _, source := range []string{"", "CREATE VIEW `a` AS SELECT 1", "/*!50001 unfinished", "/*!99999 VIEW a */", "/*!50001SELECT 1 */", "/*!50001 VIEW a */; DELETE FROM t", "/* plain */"} {
|
|
if _, ok := unwrapMySQLViewDumpStatement(source); ok {
|
|
t.Fatal("未知封装被识别成官方视图定义")
|
|
}
|
|
}
|
|
}
|