From 0dcda4f5ed5a64665f3fd1829f738feb5054b404 Mon Sep 17 00:00:00 2001 From: xuans Date: Sun, 27 Sep 2026 22:30:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DMySQL=E4=BE=8B=E7=A8=8B?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E7=BC=96=E7=A0=81=E5=B9=B6=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=8F=8C=E7=89=88=E6=9C=AC=E9=9A=94=E7=A6=BB=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 分离5.7与8.0声明头和注释编码,增加无损往返校验及中文错误上下文,补充五种异常元数据拒绝测试。 最终Windows MySQL 5.7.44与8.0.45完整专项分别通过110、121个叶子用例,零失败零跳过。后端常规回归、静态检查、模块校验及Linux测试包交叉编译通过;32个常规条件测试跳过,不作为通过证据。 同步七份任务、支持矩阵及运维文档;P0-05保持进行中,全部44项范围不变。本批不代表Linux实库、其余数据库、前端或生产验收通过。 --- .../internal/cli/mysql_routine_encoding.go | 24 ++++--- backend/internal/cli/mysql_routines.go | 6 +- .../cli/mysql_routines_integration_test.go | 47 +++++++++++++ doc/Scert开发任务清单.md | 2 +- doc/Scert整改任务执行清单.md | 10 ++- doc/Scert整改合并优化清单.md | 2 + doc/全面审查测试与优化记录.md | 2 +- doc/基础整改任务规格.md | 7 +- doc/构建版本与数据库支持矩阵.md | 67 ++++++++++++++++++- doc/生产部署与运维说明.md | 6 +- 10 files changed, 148 insertions(+), 25 deletions(-) diff --git a/backend/internal/cli/mysql_routine_encoding.go b/backend/internal/cli/mysql_routine_encoding.go index 37b635d..4a2b868 100644 --- a/backend/internal/cli/mysql_routine_encoding.go +++ b/backend/internal/cli/mysql_routine_encoding.go @@ -8,8 +8,8 @@ import ( "unicode/utf8" ) -// MySQL 8.0 将 UTF-8 目录中的声明头和注释与原客户端编码的正文拼接, -// 再按客户端字符集返回 SHOW CREATE;非 UTF-8 例程必须分段还原,不能整体反解正文。 +// 两个版本的目录注释均需单独还原,8.0 还需还原声明头; +// 非 UTF-8 例程必须分段处理,不能整体反解原客户端编码的正文。 func normalizeMySQLRoutineMetadata(ctx context.Context, tx *sql.Tx, routine mysqlRoutine, definition map[string]string, column string) error { charset := definition["character_set_client"] if charset == "utf8" || charset == "utf8mb3" || charset == "utf8mb4" { @@ -21,19 +21,23 @@ func normalizeMySQLRoutineMetadata(ctx context.Context, tx *sql.Tx, routine mysq return err } decode := func(value string) (string, error) { - var encoded []byte - query := "SELECT CAST(CONVERT(? USING " + quoteMySQLIdentifier(charset) + ") AS BINARY)" - if err := tx.QueryRowContext(ctx, query, value).Scan(&encoded); err != nil { - return "", err + var encoded, roundTrip []byte + identifier := quoteMySQLIdentifier(charset) + query := "SELECT CAST(CONVERT(? USING " + identifier + ") AS BINARY), CAST(CONVERT(CONVERT(? USING " + identifier + ") USING utf8mb4) AS BINARY)" + if err := tx.QueryRowContext(ctx, query, value, value).Scan(&encoded, &roundTrip); err != nil { + return "", fmt.Errorf("MySQL 例程目录元数据无法无损还原为 UTF-8(%s):%w", routine.name, err) } - if !utf8.Valid(encoded) { + if string(roundTrip) != value || !utf8.Valid(encoded) { return "", fmt.Errorf("MySQL 例程目录元数据无法无损还原为 UTF-8:%s", routine.name) } return string(encoded), nil } - header, err := decode(original[:end]) - if err != nil { - return err + header := original[:end] + if routine.utf8MetadataHeader { + header, err = decode(header) + if err != nil { + return err + } } var comment string var body sql.NullString diff --git a/backend/internal/cli/mysql_routines.go b/backend/internal/cli/mysql_routines.go index 740ce5f..d2ebd7e 100644 --- a/backend/internal/cli/mysql_routines.go +++ b/backend/internal/cli/mysql_routines.go @@ -115,10 +115,8 @@ SET @scert_routine_collation = @@SESSION.collation_connection;`); err != nil { if err != nil { return err } - if routine.utf8MetadataHeader { - if err := normalizeMySQLRoutineMetadata(ctx, tx, routine, definition, column); err != nil { - return err - } + if err := normalizeMySQLRoutineMetadata(ctx, tx, routine, definition, column); err != nil { + return err } // 同名例程必须替换;定义者和权限语义来自原始 SHOW CREATE,不擅自改写。 drop := "DROP " + routine.kind + " IF EXISTS " + quoteMySQLIdentifier(routine.name) diff --git a/backend/internal/cli/mysql_routines_integration_test.go b/backend/internal/cli/mysql_routines_integration_test.go index 8899177..e0ac222 100644 --- a/backend/internal/cli/mysql_routines_integration_test.go +++ b/backend/internal/cli/mysql_routines_integration_test.go @@ -59,6 +59,9 @@ func TestMySQLRealNativeRoutines(t *testing.T) { } } definition := readMySQLRoutineDefinitions(t, db.DB) + t.Run("metadata_rejection", func(t *testing.T) { + checkMySQLRoutineMetadataRejection(t, db.DB, server.IsMySQL8()) + }) output := filepath.Join(server.Root(), "native-routines.sql") if err := backupMySQLDatabase(t.Context(), source, output); err != nil { t.Fatal(err) @@ -219,6 +222,50 @@ func TestMySQLRealRoutineVisibility(t *testing.T) { } } +func checkMySQLRoutineMetadataRejection(t *testing.T, db *sql.DB, mysql8 bool) { + t.Helper() + tx, err := db.BeginTx(t.Context(), nil) + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + routine := mysqlRoutine{name: "legacy_café", kind: "FUNCTION", utf8MetadataHeader: mysql8} + const column = "Create Function" + for _, item := range []struct { + name string + alter func(string) string + message string + }{ + {"incomplete_header", func(string) string { return "CREATE FUNCTION" }, "参数列表"}, + {"body_mismatch", func(value string) string { return value + " " }, "正文与目录元数据不一致"}, + {"missing_comment", func(value string) string { + return strings.Replace(value, "\n COMMENT ", "\n REMARK ", 1) + }, "注释声明不完整"}, + {"lossy_comment", func(value string) string { + return strings.Replace(value, "\n COMMENT '", "\n COMMENT '中文", 1) + }, "无法无损还原"}, + {"invalid_utf8_comment", func(value string) string { + return strings.Replace(value, "\n COMMENT '", "\n COMMENT 'é", 1) + }, "无法无损还原"}, + } { + t.Run(item.name, func(t *testing.T) { + definition, err := readMySQLStoredDefinition(t.Context(), tx, routine.kind, routine.name, column) + if err != nil { + t.Fatal(err) + } + definition[column] = item.alter(definition[column]) + original := definition[column] + err = normalizeMySQLRoutineMetadata(t.Context(), tx, routine, definition, column) + if err == nil || !strings.Contains(err.Error(), item.message) { + t.Fatalf("无效例程元数据必须明确拒绝:%v", err) + } + if definition[column] != original { + t.Fatal("失败时不能写回部分转换的定义") + } + }) + } +} + func readMySQLRoutineDefinitions(t *testing.T, db *sql.DB) string { t.Helper() queries := []string{ diff --git a/doc/Scert开发任务清单.md b/doc/Scert开发任务清单.md index 4bf7fa2..1707e63 100644 --- a/doc/Scert开发任务清单.md +++ b/doc/Scert开发任务清单.md @@ -2,7 +2,7 @@ > 2026-09-27 任务规格:完整 44 项及六阶段验收规范见《整改任务规格与验收约定》和《Scert整改任务执行清单》。以下历史勾选不替代当前整改状态,不把旧测试结果当成新增需求已经实现。 -> 2026-09-27 当前进度:原始整改清单、执行清单和分级规格同步标记状态。P0-05 `[~]`:`3f6d5b8` 增加 MySQL 自有触发器备份、恢复顺序和可见权限预检,Windows 双版本最终全专项通过;证据见《构建版本与数据库支持矩阵》第十一节。例程、事件、视图、其余数据库和生产门禁仍未完成,不以本轮局部通过作生产放行。 +> 2026-09-27 当前进度:原始整改清单、执行清单和分级规格同步标记状态。P0-05 `[~]`:已补齐本批 MySQL 自有触发器及例程备份、可见权限和注释编码,Windows 5.7.44 / 8.0.45 最终全专项分别通过 110 / 121 个叶子用例;证据见《构建版本与数据库支持矩阵》第十二节。事件、视图、完整表清单、其他编码/SQL 模式、其余数据库与生产门禁仍未完成,不以本轮局部通过作生产放行。 > 2026-09-27 范围调整:本文件保留早期开发历史,当前执行以《Scert整改合并优化清单》和《Scert整改任务执行清单》为准。正式交付仅为 Web 和 Windows/Linux 服务端;本文件中的 Wails 桌面窗口、托盘、桌面更新、桌面配置和桌面 UI 测试均退出正式开发及验收范围,不再作为未完成交付项。CLI 限定为初始化及服务运维,不另建业务管理界面。 diff --git a/doc/Scert整改任务执行清单.md b/doc/Scert整改任务执行清单.md index 7eb5349..6b75332 100644 --- a/doc/Scert整改任务执行清单.md +++ b/doc/Scert整改任务执行清单.md @@ -39,7 +39,7 @@ | P0-02 | [x] | 建立完整任务清单 | 44 项逐项规格、原始编号名称、字段、六阶段、引用和汇总状态核对通过 | 等待最终复审,随实现同步状态和证据 | 四份规格、统一验收约定及 `fab98d1` 独立克隆 | | P0-03 | [x] | 整理源码、构建产物和部署产物 | 全仓库及子目录规则、目录说明、正式回归和独立克隆构建校验通过 | 等待最终复审,后续提交继续执行边界检查 | 正式 Go 回归、完整索引、双平台 33 项清单及解包校验 | | P0-04 | [~] | 统一开发和构建版本 | 精确工具链、安装策略、发布前后校验、系统清单及固定换行已实现,双独立克隆和完整自动化通过 | Linux 原生构建与双平台实际运行环境记录,不以交叉编译替代 | `94237b8`、175 项前端测试、后端完整回归、4 个 Web 文件一致、33 项清单和解包校验 | -| P0-05 | [~] | 确认数据库正式支持矩阵 | 已增加自有触发器导出及可见权限预检;例程、事件、视图和完整对象清单仍缺 | 补剩余对象导出、其他 SQL 模式和独立发行验证;Linux、PostgreSQL、SQLite 与正式服务门禁仍缺 | `3f6d5b8` 双版本最终完整回归分别 97、104 个叶子用例,0 失败、0 跳过;详见支持矩阵第十一节 | +| P0-05 | [~] | 确认数据库正式支持矩阵 | 已增加自有触发器和例程导出、完整例程可见权限及注释编码修复;事件、视图和完整表清单仍缺 | 补剩余对象导出、其他编码及 SQL 模式和独立发行验证;Linux、PostgreSQL、SQLite 与正式服务门禁仍缺 | 当前候选双版本完整回归分别 110、121 个叶子用例,0 失败、0 跳过;详见支持矩阵第十二节 | | P0-06 | [~] | 建立统一领域模型 | 已有域名、证书、任务、服务器等实体,项目和部署关联仍需审查 | 补齐资源关系图、外键/归属和任务关联 | 模型文档、迁移检查、接口测试 | | P0-07 | [~] | 增加项目级权限和资源隔离 | 用户组和角色已有实现,项目级资源边界未闭环 | 为证书、DNS、服务器、任务和审计补齐项目归属及越权测试 | 权限矩阵、接口越权测试 | | P0-08 | [~] | 完善凭据和主密钥生命周期 | 已有凭据存储与测试,轮换、恢复、删除和失效证据不完整 | 补齐创建、轮换、恢复、删除、脱敏和不可恢复边界 | 安全测试、日志审查、恢复演练 | @@ -350,10 +350,14 @@ P0-05 从 `[!]` 调整为 `[~]`,并未标记完成;P0-04 剩余门禁和全 ### 2026-09-27:P0-05 自有存储过程与函数备份 -修改前检查点 `db7d132` 已推送。本批接续触发器子任务,全部 44 项和原验收标准不变。 +实现前检查点 `db7d132`、未完成实现保存点 `c4051c9` 已推送。本批在保存点上修复 5.7 Latin-1 注释乱码,接续触发器子任务,全部 44 项和原验收标准不变。 - `[x]` 在独立 MySQL 5.7 实例复现例程遗漏,以及权限不足时仍成功覆盖原备份;复现证据为 `artifacts/mysql-native-routines-20260927/mysql57-before.jsonl`。 - `[x]` 增加原始例程定义导出、同名替换、创建元数据与编码保留;复用触发器的存储对象读写逻辑,先恢复数据和例程,再创建触发器。 - `[x]` 增加完整例程可见权限检查;5.7 检查 `mysql.proc` 完整读取权限,8.0 检查有效 `SHOW_ROUTINE` 或全局 `SELECT`,拒绝把隐藏对象当作不存在。 -- `[~]` 正式回归覆盖两种恢复入口、重复恢复、输入输出参数、Latin-1 名称/正文、元数据一致性、角色和部分撤销。Windows 双版本全专项、后端完整回归和交叉编译正在验证,结果以支持矩阵后续记录为准。 +- `[x]` 修复 5.7 注释双重编码,保留 8.0 声明头处理;增加字符集往返检查和错误上下文,五种无效元数据均明确拒绝且不写回部分转换结果。 +- `[x]` 正式回归覆盖两种恢复入口、重复恢复、输入输出参数、Latin-1 名称/注释/正文、元数据一致性、角色和部分撤销。最终 Windows 5.7.44 / 8.0.45 全专项分别通过 110 / 121 个叶子用例,0 失败、0 跳过;对应含父项记录为 125 / 136。 +- `[x]` 后端全量回归、vet、模块校验和 Linux CLI 测试包交叉编译通过;常规回归有 32 个条件测试跳过,不替代实库证据。前端未修改、未重测。 +- `[x]` 专项目录累计 90 个独立实例退出、167 个记录端口无监听、90 份初始化口令文件为空,共享 MySQL 进程未动;测试产物均不提交。 +- `[x]` 支持矩阵第十二节、基础规格、原始清单进度说明、历史开发清单、审查记录和生产运维说明同步更新;最终证据为 `artifacts/mysql-native-routines-20260927/mysql57-verified.jsonl`、`mysql80-verified.jsonl`。 - `[ ]` 事件、视图、完整表清单权限、其他 SQL 模式及剩余数据库/平台/生产门禁继续整改。 diff --git a/doc/Scert整改合并优化清单.md b/doc/Scert整改合并优化清单.md index 5abd259..16b5fa6 100644 --- a/doc/Scert整改合并优化清单.md +++ b/doc/Scert整改合并优化清单.md @@ -8,6 +8,8 @@ > 进度同步:2026 年 9 月 27 日。任务状态与《Scert整改任务执行清单.md》及四份分级任务规格保持一致;本文件的需求和验收标准不因状态更新而缩减。详细变更、验证命令、证据和剩余缺口见执行清单与对应专项记录,`[~]` 仅表示已有实现或正在整改,不表示整项完成。 +> 当前专项:P0-05 自有例程备份、可见权限和注释编码已完成本批 Windows 双版本隔离回归,分别通过 110、121 个叶子用例;详见支持矩阵第十二节。事件、视图、完整表清单、其余数据库/平台及生产门禁仍待完成,P0-05 保持 `[~]`,全部 44 项范围不变。 + --- ## 一、整改目标 diff --git a/doc/全面审查测试与优化记录.md b/doc/全面审查测试与优化记录.md index d419a50..e0ed6e2 100644 --- a/doc/全面审查测试与优化记录.md +++ b/doc/全面审查测试与优化记录.md @@ -2,7 +2,7 @@ > 2026-09-27 证据索引:当前 44 项整改的逐项范围、命令、实机步骤和复审字段见《整改任务规格与验收约定》及四份分级规格;实际进展以《Scert整改任务执行清单》为准。本文历史结果须按当时源码与环境解释。 -> 2026-09-27 本批进度:P0-05 `[~]`。`3f6d5b8` 修复自有 MySQL 触发器丢失和无权限仍成功的问题;最终全专项通过 5.7 的 97 个、8.0 的 104 个叶子用例,0 失败、0 跳过。权限前提、字符集/顺序验证和剩余缺口统一记录于《构建版本与数据库支持矩阵》第十一节;本批仅为 Windows 隔离实库,不是生产验收。 +> 2026-09-27 本批进度:P0-05 `[~]`。在自有触发器备份基础上补齐例程导出及完整可见权限,修复 5.7 Latin-1 注释乱码并增加五种元数据拒绝测试;最终全专项通过 5.7 的 110 个、8.0 的 121 个叶子用例,0 失败、0 跳过。先前失败与最终通过证据均保留,详见《构建版本与数据库支持矩阵》第十二节。后端回归、vet、模块校验和 Linux 交叉编译通过;没有执行 Linux 实库、前端或生产部署验收。 > 2026-09-27 范围调整:以下为历史审查证据。当前正式测试范围仅为 Web 和 Windows/Linux 服务端;历史桌面模块测试记录保留,但不再作为交付目标或生产门禁。当前任务状态见《Scert整改任务执行清单》。 diff --git a/doc/基础整改任务规格.md b/doc/基础整改任务规格.md index 1dcdbb5..46dcac5 100644 --- a/doc/基础整改任务规格.md +++ b/doc/基础整改任务规格.md @@ -63,13 +63,13 @@ - 任务编号:P0-05;任务名称:确认数据库正式支持矩阵;优先级:P0。 - 当前状态:`[~] 进行中`。 - 六阶段:需求确认 `[x]`;代码实现 `[~]`;自动化测试 `[~]`;隔离环境测试 `[~]`;生产部署测试 `[!]`;最终验收 `[ ]`。 -- 问题说明:已修复迁移、连接恢复、备份会话、精确数值、跨表快照、失败文件保护、生成列和不可见列导出;支持本批官方存储对象 SQL 的分隔符恢复,并新增自有触发器导出与可见权限预检。例程、事件、视图等对象完整性、其他 SQL 模式及独立发行验证仍不足。 +- 问题说明:已修复迁移、连接恢复、备份会话、精确数值、跨表快照、失败文件保护、生成列和不可见列导出;支持本批官方存储对象 SQL 的分隔符恢复,并新增自有触发器、例程导出及可见权限预检。例程注释编码已复测;事件、视图、完整表清单、其他编码与 SQL 模式及独立发行验证仍不足。 - 涉及文件:`backend/internal/database/`、其 `migrations/mysql/`、`migrations/postgres/`、`migrations/sqlite/`、`backend/internal/cli/`、《构建版本与数据库支持矩阵.md》。 - 依赖任务:P0-04。 - 整改内容:明确 SQLite、MySQL、PostgreSQL 支持版本;验证 MySQL 5.7/8.0、PostgreSQL 目标版本、SQLite 锁并发;逐库检查类型、索引、默认值、事务及初始化、升级、回滚、备份恢复。不能悄然去掉难测版本。 - 验证命令:V06、V02;真实数据库测试按正式集成测试的参数运行并保存非跳过结果,不在命令行嵌入密码。 - 实机验收步骤:逐版本创建专用测试库;从空库初始化及历史版本迁移,注入失败检查事务;使用官方备份工具恢复并核对行数、资源关联和加密凭据;验证 TLS 和断线重连;只清理明确测试库。 -- 验收证据:在既有迁移、连接恢复、快照和失败边界基础上,`f260b22` 增加特殊列双向官方工具互操作,`e6e6719` 增加官方存储对象恢复。最终候选在 Windows MySQL 5.7.44、8.0.45 分别通过 85、87 个叶子用例,0 失败、0 跳过;对应含父项记录为 95、97。已实际执行触发器、过程、函数和隔离事件。详见支持矩阵第九、十节及执行清单。 +- 验收证据:在既有迁移、连接恢复、特殊列互操作和官方存储对象恢复基础上,补齐自有触发器及例程备份、权限检查、注释编码和元数据拒绝测试。当前候选在 Windows MySQL 5.7.44、8.0.45 分别通过 110、121 个叶子用例,0 失败、0 跳过;对应含父项记录为 125、136。已实际执行触发器、过程、函数和隔离事件;详见支持矩阵第十二节的最终证据及执行清单。 - 复审结果:上述 Windows 隔离范围通过,不等于生产放行。只验证 InnoDB 数据快照,不承诺并发 DDL 一致性;非事务表和视图拒绝属于临时边界。官方导出文件恢复通过不证明自有导出保存了全部对象;其他 SQL 模式、定义者迁移、独立发行命令、Linux 实库、PostgreSQL 当前复测、SQLite 并发锁及正式服务验收仍未完成。 ### P0-05 触发器自有备份子任务进度 @@ -85,7 +85,8 @@ - `[x]` 复现并修复存储过程、函数遗漏;保留定义者、参数、SQL 模式、创建字符集与排序规则,支持替换同名例程。 - `[x]` 不依赖过滤后的元数据空列表;完整查看权限不足时失败,保留已有备份并清理临时文件。 -- `[~]` 双版本正式实库回归、角色和部分撤销验证、完整后端回归及 Linux 测试包交叉编译。 +- `[x]` 修复 5.7 Latin-1 例程注释乱码,保留 8.0 声明头处理;新增五种元数据失败保护场景,错误保留中文上下文及底层原因。 +- `[x]` 双版本正式实库全专项通过 110 / 121 个叶子用例,0 失败、0 跳过;完整后端回归、vet、模块校验及 Linux 测试包交叉编译通过。收尾与证据见支持矩阵第十二节;未执行 Linux 实库,不提升总状态。 - `[ ]` 事件、视图、完整表清单权限、其他 SQL 模式、跨服务器定义者迁移及生产恢复验收。本子任务不提升 P0-05 总状态。 ## P0-06 建立统一领域模型 diff --git a/doc/构建版本与数据库支持矩阵.md b/doc/构建版本与数据库支持矩阵.md index f210c3c..9606de9 100644 --- a/doc/构建版本与数据库支持矩阵.md +++ b/doc/构建版本与数据库支持矩阵.md @@ -56,8 +56,8 @@ npm.cmd run build | 数据库 | 预期定位 | 代码现状 | 本轮验证 | 尚未满足的正式验收 | |---|---|---|---|---| | SQLite | 单机、开发、轻量部署 | 驱动、初始化、迁移 1~9、CLI 备份恢复 | 临时独立库从版本 0~9 升级、重复迁移、非法历史和新版数据库拒绝 | 并发读写/锁、服务重启、完整业务数据升级恢复 | -| MySQL 5.7.44 | 历史兼容目标,仍保留实现 | 独立方言、迁移、连接恢复、InnoDB 快照、生成列备份及官方存储对象 SQL 恢复 | `e6e6719`:85 个叶子用例通过,含既有回归、特殊列双向互操作和存储对象恢复;详见第九、十节 | 自有导出的对象完整性、更多 SQL 模式、独立发行命令、Linux 实库、Web 初始化及生产系统验证 | -| MySQL 8.0.45 | 正式生产支持目标 | 同上,额外处理不可见列实际数据 | 同候选独立实库 87 个叶子用例通过,未复用 5.7 的结果 | 同上;不代表 8.0 全部补丁版本已经验证 | +| MySQL 5.7.44 | 历史兼容目标,仍保留实现 | 独立方言、迁移、连接恢复、InnoDB 快照、特殊列、官方存储对象恢复及自有触发器和例程备份 | 当前候选 110 个叶子用例通过,含例程注释编码、元数据拒绝及可见权限;详见第十二节 | 事件、视图、完整表清单、更多编码与 SQL 模式、独立发行命令、Linux 实库、Web 初始化及生产系统验证 | +| MySQL 8.0.45 | 正式生产支持目标 | 同上,额外处理不可见列实际数据、启用角色与部分撤销 | 同候选独立实库 121 个叶子用例通过,未复用 5.7 的结果;详见第十二节 | 同上;不代表 8.0 全部补丁版本已经验证 | | PostgreSQL | 正式生产支持目标,实际服务端版本待本轮复核 | 独立方言、迁移 1~9、严格 TLS 和官方备份恢复工具接入 | 文件/嵌入/登记一致性、凭据/TLS 单元测试 | 当前版本真实集群、初始化、升级、失败回滚、备份恢复和业务回归 | 正式支持目标不等于已经生产放行。旧文档中的 PostgreSQL 实机记录仅作为线索,不替代当前候选版本复测。本批 MySQL 测试自行初始化 loopback 独立实例,不连接本机已有服务或生产数据库;PostgreSQL 当前候选实库测试未执行。 @@ -361,3 +361,66 @@ go test -json -p 1 -count=1 -timeout=8m -run '^TestMySQLReal' ./internal/databas - 本轮只证明已列出的基础表上触发器可见,仍需补齐完整表清单及例程、事件、视图的权限与导出;不能把不可见表当作不存在。非 InnoDB 表及视图仍为临时拒绝边界。 - `NO_BACKSLASH_ESCAPES`、其他非 UTF-8 多字节编码、跨账户/跨服务器定义者、跨库引用、备份期间 DDL/授权变化、恢复中途失败后的数据库排序规则回退仍需单独验证;当前词法预检不是 SQL 语义预检或 DDL 原子恢复。 - 独立发行 CLI、Linux 实库、PostgreSQL、SQLite、Web 业务及生产门禁保持未完成。全部 44 项继续保留,P0-05 不作生产放行。 + +## 十二、MySQL 自有例程备份与注释编码(2026-09-27) + +### 状态与实现 + +本节接续第十一节的例程缺口,不改变全部 44 项和 P0-05 的三库、双平台要求。原始实现前检查点为 `db7d132`,未完成实现已在 `c4051c9` 保存并推送。本轮在该检查点上修复已复现的 5.7 Latin-1 注释乱码,并补充拒绝异常元数据的正式测试;最终验证结果另列于本节末尾,P0-05 总状态仍为 `[~]`。 + +- 自有导出包含存储过程、函数的原始定义、参数、定义者、SQL 模式、客户端字符集、连接及数据库排序规则。数据全部恢复后创建例程,再创建依赖例程的触发器。 +- 同名例程先删除再创建,支持重复恢复;删除和创建语句均按原客户端字符集输出并验证转换往返一致,分隔符避开正文冲突。 +- 5.7 直接证明 `mysql.proc` 完整读取权限并使用该目录列出例程;8.0 检查当前有效授权,包括启用角色和嵌套角色,要求 `SHOW_ROUTINE` 或没有被当前库部分撤销限制的全局 `SELECT`。 +- 即使元数据为空也不能省略权限检查。权限不足时保留已有备份,并清理本次临时文件;程序不对真实账户自动扩权。 + +### 复现与编码修复 + +先前证据 `mysql57-before.jsonl` 复现例程遗漏与权限不足仍覆盖原备份。扩展编码场景后,`mysql57-encoding.jsonl` 记录 Latin-1 注释从 `café;;` 变为 `café;;`;`mysql80-encoding.jsonl` 的定向通过不能覆盖这个 5.7 缺陷。 + +本轮将声明头与注释的处理分开:5.7 保留原声明头,8.0 还原目录声明头;两版都单独还原目录注释,不整体反解正文。反解必须同时满足字符集往返一致和 UTF-8 有效,不能静默替换无法表达的字符。正文与目录定义不一致时明确拒绝,不猜测边界或编码。 + +初次完整复测 `mysql57-final.jsonl` 通过;`mysql80-final.jsonl` 中新增有损注释拒绝用例发现 8.0 返回原始错误 3988,缺少例程上下文。随后补充中文上下文并保留底层错误,未删除或放宽失败断言。最终候选证据单独保存为 `mysql57-verified.jsonl`、`mysql80-verified.jsonl`,不能引用前一批结果代替最终候选结果。 + +### 正式测试范围 + +`TestMySQLRealNativeRoutines` 创建三个例程及一个依赖函数的触发器:包含 DECIMAL 参数、IN/INOUT/OUT 参数、中文说明、SQL SECURITY INVOKER、带反引号的名称,以及 Latin-1 名称、注释和正文。 + +1. 分别使用 Scert 和官方 `mysql` 导入自有备份,每条路径重复恢复两次。 +2. 目标预建同名错误函数,要求被正确替换;逐字段比较例程和参数元数据。 +3. 实际调用函数和过程,并执行依赖函数的触发器;检查原始数据没有被导入触发器改写。 +4. 检查数据库排序规则恢复;非 ASCII 名称、注释和正文须保持原值。 +5. 增加五个元数据拒绝场景:声明头不完整、正文不一致、注释声明缺失、有损转换、无效 UTF-8。失败时定义不能被部分改写。 + +`TestMySQLRealRoutineVisibility` 在 5.7 覆盖六种权限场景,在 8.0 覆盖十种场景。包括隐藏例程、无权限空库、全局 SELECT、仅 EXECUTE、列级 SELECT、角色及部分撤销;仅 EXECUTE 的对象实际可见但正文为 NULL。部分撤销场景先移除夹具自带库级 SELECT,避免把撤销库授权误认为已经限制了全局 SELECT;嵌套角色不保留直接角色授予。 + +全部测试仅使用每次新建的 loopback 独立实例、随机账户和严格 TLS,不接受现有服务连接串,不清理共享或生产数据库,不新增辅助验收脚本。Linux 交叉编译不等同于 Linux 实库或独立发行 CLI 验收。 + +### 复测命令与结果 + +在 `backend` 目录执行,证据目录为 `artifacts/mysql-native-routines-20260927/`: + +```powershell +$env:SCERT_MYSQL_TEST_ROOT = 'D:\Desktop\Scert\artifacts\mysql-native-routines-20260927' +$env:SCERT_MYSQL_TEST_BIN = 'E:\DB\5.7\bin' +go test -json -p 1 -count=1 -timeout=8m -run '^TestMySQLReal' ./internal/database ./internal/cli | + Tee-Object -FilePath '../artifacts/mysql-native-routines-20260927/mysql57-verified.jsonl' +# 8.0 使用 E:\DB\8.0\bin,另存为 mysql80-verified.jsonl。 +``` + +本批例程子任务状态:`[x] 已完成,等待复审`。最终候选以两个 `verified` 文件为准: + +| 实际版本 | 数据库包 | CLI 包 | 通过记录(含父项) | 叶子用例 | 失败 / 跳过 | +|---|---:|---:|---:|---:|---:| +| MySQL 5.7.44 | 35.018 秒 | 75.912 秒 | 125 | 110 | 0 / 0 | +| MySQL 8.0.45 | 57.781 秒 | 94.388 秒 | 136 | 121 | 0 / 0 | + +后端 `go test -json -count=1 ./...` 结果另存为 `backend-verified.jsonl`:28 个有测试的包通过,1340 个通过记录(含父项)、1234 个通过叶子用例、0 失败;32 个测试因未启用对应实库或平台条件而跳过,不能算作通过。MySQL 非跳过实库证据以上表为准,不用常规回归替代。`go vet ./...`、`go mod verify` 均通过,Linux amd64 CLI 测试包交叉编译通过但未在 Linux 执行;前端未修改、未重测。PowerShell 5.1 的 JSONL 使用 UTF-16,复核用 `Get-Content -Encoding Unicode`。 + +收尾复核:本专项目录累计 90 个隔离实例均退出,167 个已记录端口无监听,90 份初始化口令文件均为空;原有三个共享 MySQL 进程保持不变。数据、随机测试密钥、原始日志和编译产物只留在忽略目录,不提交。上述数字包含前一轮复现和失败证据,不是本次通过用例数。 + +### 剩余门禁 + +- 事件、视图和完整表清单的导出及可见权限仍未完成,不能把隐藏表或其他对象当作不存在。 +- 其他多字节编码、非 UTF-8 返回枚举类型、`NO_BACKSLASH_ESCAPES` 等 SQL 模式、跨账户或跨服务器定义者、跨库引用和备份期间 DDL/授权变化仍需验证。 +- 保留原始 `DEFINER`;不会自动迁移账户授权或清除备份外的额外目标例程。恢复失败后的 DDL 原子性和数据库排序规则回退尚无完整承诺。 +- 独立发行 CLI、Linux 实库、PostgreSQL、SQLite、正式 Web 业务与全部生产验收门禁继续保留。新增例程通过不提升 P0-05 总状态,也不作生产放行。 diff --git a/doc/生产部署与运维说明.md b/doc/生产部署与运维说明.md index 6d74641..e462d54 100644 --- a/doc/生产部署与运维说明.md +++ b/doc/生产部署与运维说明.md @@ -273,7 +273,11 @@ MySQL 账号和密码从加密数据库凭据引用读取,只在维护连接 MySQL 自有备份已增加触发器导出。维护账户除读取表结构/数据外,还需对每张可见基础表具有可证明的有效 `TRIGGER` 权限;没有权限时,数据库可能隐藏触发器,Scert 会明确拒绝而不是报告不完整备份成功。即使业务当前没有触发器,也不能据此省略该可见权限。产品不会自动扩权;精确库级或表级授权由管理员审核配置,复杂且存在遮蔽的通配授权可能被保守拒绝。 -恢复时先写入所有表数据,再创建触发器,并保留原定义者、SQL 模式和创建字符集。还需目标数据库的 `ALTER` 及实际服务器对定义者、二进制日志的权限要求;不能照搬隔离测试账户的全局管理权限。自有导出对例程、事件、视图及完整对象清单的支持尚未补齐,触发器验证不能作为完整 MySQL 灾难恢复验收。具体限制和当前证据见《构建版本与数据库支持矩阵》第十一节。 +MySQL 自有备份同时导出存储过程和函数。5.7 维护账户需对 `mysql.proc` 具有完整 `SELECT` 权限;8.0 需具有有效 `SHOW_ROUTINE`,或没有被当前库部分撤销限制的全局 `SELECT`。仅有库级 `EXECUTE`、`CREATE ROUTINE` 或元数据中看不到例程,不能证明完整定义可见;即使源库当前没有例程,检查仍会执行。8.0 支持通过已启用角色获得权限,不会自动启用角色或扩权。请优先由管理员评估专用维护账户及最小可见权限,不给业务账户照搬测试授权。 + +恢复时先写入所有表数据,再创建例程,最后创建触发器,保留原定义者、SQL 模式和创建字符集;同名例程会先删除再创建。目标账户需具备相应对象管理权限、数据库 `ALTER` 权限,并满足实际服务器对定义者和二进制日志的权限要求。不能照搬隔离测试账户的全局管理权限,也不能通过开启全局信任来绕过验证。 + +自有导出对事件、视图及完整表清单的支持尚未补齐;例程目前验证了 UTF-8 和 Latin-1 的指定场景,不代表任意字符集、SQL 模式或跨服务器定义者迁移通过。成功恢复后还原会话属性和源库数据库排序规则;中途失败的 DDL 原子回滚及排序规则回退仍需验证。不会清除备份之外的额外目标例程,也不恢复数据库账号授权。具体证据和未完成边界见《构建版本与数据库支持矩阵》第十一、十二节,不能据此宣布完整 MySQL 灾难恢复通过。 ### 7.2 停服备份