From 863d3c7c394fcc3dcb7b2c1fea841c41bed1c520 Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Mon, 14 Sep 2026 11:21:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90=EF=BC=9A=E8=A1=A5=E5=85=A8Kingbase=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=85=83=E6=95=B0=E6=8D=AEIdentity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../001_generator_identity_precheck.sql | 91 ++++++++ .../002a_gen_table_identity_patch.sql | 22 ++ .../002b_gen_table_column_identity_patch.sql | 22 ++ .../003_generator_identity_verify.sql | 146 +++++++++++++ RuoYi-Vue/sql/generator/kingbase/README.md | 79 +++++++ .../01_kingbase_import_identity_patch.md | 202 ++++++++++++++++++ 6 files changed, 562 insertions(+) create mode 100644 RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql create mode 100644 RuoYi-Vue/sql/generator/kingbase/002a_gen_table_identity_patch.sql create mode 100644 RuoYi-Vue/sql/generator/kingbase/002b_gen_table_column_identity_patch.sql create mode 100644 RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql create mode 100644 RuoYi-Vue/sql/generator/kingbase/README.md create mode 100644 docs/generator-v2/01_kingbase_import_identity_patch.md diff --git a/RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql b/RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql new file mode 100644 index 000000000..cb61e7720 --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql @@ -0,0 +1,91 @@ +-- ============================================================================== +-- 001_generator_identity_precheck.sql +-- Generator Kingbase Identity 预检只读脚本 (增强版: 支持 Partial 状态与数据量探测) +-- +-- 功能:只读探测 gen_table.table_id 与 gen_table_column.column_id 的 Identity 状态 +-- 判定准则: +-- 1. CASE_1_BOTH_NEED_PATCH: 两列均缺失 Identity,需依次执行 002a 和 002b +-- 2. CASE_2_BOTH_ALREADY_PATCHED: 两列均已具备 Identity,禁止重复执行任何 Patch +-- 3. CASE_3_ONLY_COLUMN_NEEDS_PATCH: 仅 column_id 缺失,严禁执行 002a,仅执行 002b +-- 4. CASE_4_ONLY_TABLE_NEEDS_PATCH: 仅 table_id 缺失,严禁执行 002b,仅执行 002a +-- 安全说明:纯只读查询,无任何 DDL / DML / 序列修改副作用 +-- ============================================================================== + +-- 1. 列级别 Identity 详细属性检查 +SELECT + c.relname AS table_name, + a.attname AS column_name, + t.typname AS data_type, + a.attnotnull AS is_not_null, + COALESCE(a.attidentity, '') AS attidentity, + CASE a.attidentity + WHEN 'd' THEN 'ALREADY_PATCHED (GENERATED BY DEFAULT AS IDENTITY)' + WHEN 'a' THEN 'ALREADY_PATCHED (GENERATED ALWAYS AS IDENTITY)' + ELSE 'NEED_PATCH (MISSING_IDENTITY)' + END AS patch_status +FROM sys_class c +JOIN sys_attribute a ON a.attrelid = c.oid +JOIN sys_type t ON a.atttypid = t.oid +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) +ORDER BY c.relname, a.attname; + +-- 2. 检查关联 Sequence 存在性 +SELECT + c.relname AS sequence_name, + c.relkind AS relkind, + CASE c.relkind + WHEN 'S' THEN 'SEQUENCE_EXISTS' + ELSE 'UNKNOWN' + END AS seq_status +FROM sys_class c +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND c.relname IN ('gen_table_table_id_seq', 'gen_table_column_column_id_seq') +ORDER BY c.relname; + +-- 3. 现有数据行数与最大 ID 统计 (区分空表与已有数据环境) +SELECT + 'gen_table' AS table_name, + COUNT(*) AS row_count, + COALESCE(MAX(table_id), 0) AS max_id, + CASE WHEN COUNT(*) = 0 THEN 'EMPTY_METADATA_ENV (默认起始序列即可安全使用)' + ELSE 'EXISTING_METADATA_ENV (补丁后需重点核验 Sequence 对齐)' END AS env_type +FROM gen_table +UNION ALL +SELECT + 'gen_table_column' AS table_name, + COUNT(*) AS row_count, + COALESCE(MAX(column_id), 0) AS max_id, + CASE WHEN COUNT(*) = 0 THEN 'EMPTY_METADATA_ENV (默认起始序列即可安全使用)' + ELSE 'EXISTING_METADATA_ENV (补丁后需重点核验 Sequence 对齐)' END AS env_type +FROM gen_table_column; + +-- 4. 全局 Partial / Full 状态判定与执行建议 +WITH status_data AS ( + SELECT + MAX(CASE WHEN c.relname = 'gen_table' AND a.attname = 'table_id' THEN COALESCE(a.attidentity, '') END) AS table_id_id, + MAX(CASE WHEN c.relname = 'gen_table_column' AND a.attname = 'column_id' THEN COALESCE(a.attidentity, '') END) AS column_id_id + FROM sys_class c + JOIN sys_attribute a ON a.attrelid = c.oid + JOIN sys_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) +) +SELECT + CASE + WHEN table_id_id IN ('d', 'a') AND column_id_id IN ('d', 'a') THEN + 'CASE_2_BOTH_ALREADY_PATCHED: 两列均已配置 Identity,禁止执行任何 Patch,直接运行 003_generator_identity_verify.sql' + WHEN table_id_id NOT IN ('d', 'a') AND column_id_id NOT IN ('d', 'a') THEN + 'CASE_1_BOTH_NEED_PATCH: 两列均缺失 Identity,需依次执行 002a_gen_table_identity_patch.sql 与 002b_gen_table_column_identity_patch.sql' + WHEN table_id_id IN ('d', 'a') AND column_id_id NOT IN ('d', 'a') THEN + 'CASE_3_ONLY_COLUMN_NEEDS_PATCH: 仅 column_id 缺失,严禁执行 002a,仅可执行 002b_gen_table_column_identity_patch.sql' + WHEN table_id_id NOT IN ('d', 'a') AND column_id_id IN ('d', 'a') THEN + 'CASE_4_ONLY_TABLE_NEEDS_PATCH: 仅 table_id 缺失,严禁执行 002b,仅可执行 002a_gen_table_identity_patch.sql' + ELSE + 'CASE_UNKNOWN: 异常状态,请联系管理员核实' + END AS precheck_scenario_verdict +FROM status_data; diff --git a/RuoYi-Vue/sql/generator/kingbase/002a_gen_table_identity_patch.sql b/RuoYi-Vue/sql/generator/kingbase/002a_gen_table_identity_patch.sql new file mode 100644 index 000000000..36ca07a53 --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/002a_gen_table_identity_patch.sql @@ -0,0 +1,22 @@ +-- ============================================================================== +-- 002a_gen_table_identity_patch.sql +-- Generator Kingbase Identity 独立补丁 A: gen_table.table_id +-- +-- 变更目标: +-- 为 public.gen_table.table_id 添加 GENERATED BY DEFAULT AS IDENTITY +-- +-- 执行准入前置条件(强制要求): +-- 1. 必须先执行 001_generator_identity_precheck.sql; +-- 2. 仅当 Precheck 判定结果为 CASE_1_BOTH_NEED_PATCH 或 CASE_4_ONLY_TABLE_NEEDS_PATCH 时方可执行; +-- 3. 若 table_id 已为 ALREADY_PATCHED,严禁执行本脚本(Kingbase 将抛出 already an identity column 错误)。 +-- +-- 环境数据安全约束: +-- - 本脚本仅添加 Identity 特性,默认从 1 开始; +-- - 若当前 gen_table 为空表 (row_count = 0),执行后直接安全; +-- - 若当前 gen_table 已存在历史元数据 (row_count > 0),执行后必须通过 003 脚本核对 Sequence 对齐状态; +-- - 严禁 DROP 表、重建表或清空 Generator 元数据。 +-- ============================================================================== + +ALTER TABLE gen_table +ALTER COLUMN table_id +ADD GENERATED BY DEFAULT AS IDENTITY; diff --git a/RuoYi-Vue/sql/generator/kingbase/002b_gen_table_column_identity_patch.sql b/RuoYi-Vue/sql/generator/kingbase/002b_gen_table_column_identity_patch.sql new file mode 100644 index 000000000..7ea4c9f7c --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/002b_gen_table_column_identity_patch.sql @@ -0,0 +1,22 @@ +-- ============================================================================== +-- 002b_gen_table_column_identity_patch.sql +-- Generator Kingbase Identity 独立补丁 B: gen_table_column.column_id +-- +-- 变更目标: +-- 为 public.gen_table_column.column_id 添加 GENERATED BY DEFAULT AS IDENTITY +-- +-- 执行准入前置条件(强制要求): +-- 1. 必须先执行 001_generator_identity_precheck.sql; +-- 2. 仅当 Precheck 判定结果为 CASE_1_BOTH_NEED_PATCH 或 CASE_3_ONLY_COLUMN_NEEDS_PATCH 时方可执行; +-- 3. 若 column_id 已为 ALREADY_PATCHED,严禁执行本脚本(Kingbase 将抛出 already an identity column 错误)。 +-- +-- 环境数据安全约束: +-- - 本脚本仅添加 Identity 特性,默认从 1 开始; +-- - 若当前 gen_table_column 为空表 (row_count = 0),执行后直接安全; +-- - 若当前 gen_table_column 已存在历史元数据 (row_count > 0),执行后必须通过 003 脚本核对 Sequence 对齐状态; +-- - 严禁 DROP 表、重建表或清空 Generator 元数据。 +-- ============================================================================== + +ALTER TABLE gen_table_column +ALTER COLUMN column_id +ADD GENERATED BY DEFAULT AS IDENTITY; diff --git a/RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql b/RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql new file mode 100644 index 000000000..1f57a16db --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql @@ -0,0 +1,146 @@ +-- ============================================================================== +-- 003_generator_identity_verify.sql +-- Generator Kingbase Identity 验证只读脚本 (增强版: 含 Sequence 对齐安全核验) +-- +-- 功能:只读验证 gen_table 与 gen_table_column 的 Identity 状态及 Sequence 安全性 +-- 验证维度: +-- 1. 列 Identity 属性配置:attidentity 精确等于 'd' +-- 2. Sequence 存在性:gen_table_table_id_seq / gen_table_column_column_id_seq 均为 'S' +-- 3. Sequence 依赖关系:sys_depend 中记录类型为 'i' (internal identity dependency) +-- 4. Sequence 对齐安全位:下一个分配 ID 必须严格大于现有 MAX(id) (或空表 SAFE) +-- 安全说明:纯只读查询,无任何 DDL / DML / nextval / setval 副作用 +-- ============================================================================== + +-- 1. 验证列级别 Identity 配置 +SELECT + c.relname AS table_name, + a.attname AS column_name, + a.attidentity AS attidentity, + CASE + WHEN a.attidentity = 'd' THEN 'PASS (GENERATED BY DEFAULT AS IDENTITY)' + ELSE 'FAIL (ATTIDENTITY_NOT_D)' + END AS identity_check +FROM sys_class c +JOIN sys_attribute a ON a.attrelid = c.oid +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) +ORDER BY c.relname, a.attname; + +-- 2. 验证 Sequence 存在性与类别 +SELECT + c.relname AS sequence_name, + c.relkind AS relkind, + CASE + WHEN c.relkind = 'S' THEN 'PASS (SEQUENCE_EXISTS)' + ELSE 'FAIL (NOT_A_SEQUENCE)' + END AS sequence_check +FROM sys_class c +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND c.relname IN ('gen_table_table_id_seq', 'gen_table_column_column_id_seq') +ORDER BY c.relname; + +-- 3. 验证 Sequence 依赖关系 (deptype = 'i') +SELECT + s.relname AS sequence_name, + t.relname AS target_table, + a.attname AS target_column, + d.deptype AS dependency_type, + CASE + WHEN d.deptype = 'i' THEN 'PASS (INTERNAL_IDENTITY_DEPENDENCY)' + ELSE 'FAIL (INVALID_DEPENDENCY_TYPE)' + END AS dependency_check +FROM sys_depend d +JOIN sys_class s ON s.oid = d.objid +JOIN sys_class t ON t.oid = d.refobjid +JOIN sys_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid +JOIN sys_namespace n ON n.oid = t.relnamespace +WHERE n.nspname = 'public' + AND t.relname IN ('gen_table', 'gen_table_column') + AND a.attname IN ('table_id', 'column_id') +ORDER BY t.relname, a.attname; + +-- 4. 验证 Sequence 当前位置与已有 MAX(id) 对齐安全性 (只读) +SELECT + 'gen_table' AS table_name, + 'table_id' AS column_name, + t.row_count, + t.max_id AS existing_max_id, + s.last_value::bigint AS sequence_last_value, + s.is_called, + CASE + WHEN t.row_count = 0 THEN 'EMPTY_TABLE_SAFE' + WHEN (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > t.max_id THEN 'SAFE' + ELSE 'UNSAFE (SEQUENCE_BEHIND_OR_EQUAL_MAX_ID)' + END AS seq_alignment_status +FROM (SELECT COUNT(*) AS row_count, COALESCE(MAX(table_id), 0) AS max_id FROM gen_table) t, + gen_table_table_id_seq s +UNION ALL +SELECT + 'gen_table_column' AS table_name, + 'column_id' AS column_name, + c.row_count, + c.max_id AS existing_max_id, + s.last_value::bigint AS sequence_last_value, + s.is_called, + CASE + WHEN c.row_count = 0 THEN 'EMPTY_TABLE_SAFE' + WHEN (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > c.max_id THEN 'SAFE' + ELSE 'UNSAFE (SEQUENCE_BEHIND_OR_EQUAL_MAX_ID)' + END AS seq_alignment_status +FROM (SELECT COUNT(*) AS row_count, COALESCE(MAX(column_id), 0) AS max_id FROM gen_table_column) c, + gen_table_column_column_id_seq s; + +-- 5. 总体综合判定 (包含属性配置、序列存在、依赖关系及 Sequence 对齐安全) +WITH identity_summary AS ( + SELECT count(*) AS id_cnt + FROM sys_class c + JOIN sys_attribute a ON a.attrelid = c.oid + JOIN sys_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) + AND a.attidentity = 'd' +), +seq_summary AS ( + SELECT count(*) AS seq_cnt + FROM sys_class c + JOIN sys_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = 'public' + AND c.relname IN ('gen_table_table_id_seq', 'gen_table_column_column_id_seq') + AND c.relkind = 'S' +), +dep_summary AS ( + SELECT count(*) AS dep_cnt + FROM sys_depend d + JOIN sys_class s ON s.oid = d.objid + JOIN sys_class t ON t.oid = d.refobjid + JOIN sys_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid + JOIN sys_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'public' + AND t.relname IN ('gen_table', 'gen_table_column') + AND a.attname IN ('table_id', 'column_id') + AND d.deptype = 'i' +), +seq_align AS ( + SELECT + sum(CASE WHEN t.row_count = 0 OR (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > t.max_id THEN 1 ELSE 0 END) AS safe_cnt + FROM ( + SELECT 'gen_table' AS tbl, COUNT(*) AS row_count, COALESCE(MAX(table_id), 0) AS max_id FROM gen_table + ) t, gen_table_table_id_seq s + UNION ALL + SELECT + sum(CASE WHEN c.row_count = 0 OR (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > c.max_id THEN 1 ELSE 0 END) AS safe_cnt + FROM ( + SELECT 'gen_table_column' AS tbl, COUNT(*) AS row_count, COALESCE(MAX(column_id), 0) AS max_id FROM gen_table_column + ) c, gen_table_column_column_id_seq s +) +SELECT + CASE + WHEN i.id_cnt = 2 AND s.seq_cnt = 2 AND d.dep_cnt = 2 AND (SELECT sum(safe_cnt) FROM seq_align) = 2 + THEN 'VERIFY: PASS (所有 Identity、Sequence、依赖关系与序列对齐安全核验一致通过)' + ELSE 'VERIFY: FAIL (存在核验未达标或 Sequence 落后风险,详情见分项输出)' + END AS overall_verify_result +FROM identity_summary i, seq_summary s, dep_summary d; diff --git a/RuoYi-Vue/sql/generator/kingbase/README.md b/RuoYi-Vue/sql/generator/kingbase/README.md new file mode 100644 index 000000000..01998d32d --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/README.md @@ -0,0 +1,79 @@ +# Kingbase Generator Identity 基础设施补丁 + +## 1. 补丁目标 +解决 RuoYi 代码生成器(Generator)在 KingbaseES 数据库下执行表结构导入(`importGenTable`)时,因 `gen_table.table_id` 和 `gen_table_column.column_id` 缺失自增/Identity 属性,导致 `useGeneratedKeys="true"` 回填失败并引发主键冲突(Duplicate key violation)的运行时兼容性问题。 + +## 2. 补丁范围与安全红线 +- **涉及对象**:严格仅限 `public.gen_table.table_id` 与 `public.gen_table_column.column_id`。 +- **安全红线**: + - 严禁修改 21 张 `train_*` 业务表; + - 严禁修改 `sys_*` 系统/权限/字典表; + - 严禁 DROP 表、重建表或清空/截断 Generator 历史元数据; + - 严禁在未做 Precheck 判定前盲目全量执行 Patch。 + +## 3. 文件清单 + +| 文件名 | 类型 | 说明 | +| :--- | :---: | :--- | +| `001_generator_identity_precheck.sql` | 只读探测 | 检查两列 Identity 配置、现有数据量及全局 Case 场景。 | +| `002a_gen_table_identity_patch.sql` | DDL 补丁 | 仅为 `gen_table.table_id` 添加 `GENERATED BY DEFAULT AS IDENTITY`。 | +| `002b_gen_table_column_identity_patch.sql` | DDL 补丁 | 仅为 `gen_table_column.column_id` 添加 `GENERATED BY DEFAULT AS IDENTITY`。 | +| `003_generator_identity_verify.sql` | 只读验证 | 综合校验列属性、Sequence 存在、依赖关系及 Sequence 对齐安全性。 | + +--- + +## 4. 四种 Partial / Full 执行决策矩阵 + +根据 `001_generator_identity_precheck.sql` 查询 4 返回的 `precheck_scenario_verdict`,严格按以下矩阵执行: + +| 场景编号 | Precheck 判定结果 | 执行动作 | +| :--- | :--- | :--- | +| **Scenario A** | `CASE_1_BOTH_NEED_PATCH`
(两列均缺失 Identity) | 1. 执行 `002a_gen_table_identity_patch.sql`
2. 执行 `002b_gen_table_column_identity_patch.sql`
3. 执行 `003_generator_identity_verify.sql` | +| **Scenario B** | `CASE_2_BOTH_ALREADY_PATCHED`
(两列均已配置 Identity) | **严禁执行任何 Patch!**
直接执行 `003_generator_identity_verify.sql` 复核。 | +| **Scenario C** | `CASE_3_ONLY_COLUMN_NEEDS_PATCH`
(仅 `column_id` 缺失) | **严禁执行 002a!**
1. 仅执行 `002b_gen_table_column_identity_patch.sql`
2. 执行 `003_generator_identity_verify.sql` | +| **Scenario D** | `CASE_4_ONLY_TABLE_NEEDS_PATCH`
(仅 `table_id` 缺失) | **严禁执行 002b!**
1. 仅执行 `002a_gen_table_identity_patch.sql`
2. 执行 `003_generator_identity_verify.sql` | + +--- + +## 5. 数据环境分类与 Sequence 对齐要求 + +### 5.1 Case A:Generator 元数据为空(0 rows,全新环境) +- `001` 预检中 `env_type` 为 `EMPTY_METADATA_ENV`; +- 执行 Patch 后,Kingbase 自动生成的 Sequence 从 1 开始分配; +- 执行 `003` 核验,Sequence 对齐状态将显示 `EMPTY_TABLE_SAFE`,可直接投入使用。 + +### 5.2 Case B:Generator 元数据已存在(已有历史导入数据) +- `001` 预检中 `env_type` 为 `EXISTING_METADATA_ENV`; +- **核心风险**:Kingbase `ADD GENERATED BY DEFAULT AS IDENTITY` 在已有数据环境下默认从 1 开始分配,**不宣称自动对齐已有数据 MAX(id)**! +- **操作要求**: + 1. 执行对应补丁后,**必须立即执行 `003_generator_identity_verify.sql`**; + 2. 观察验证输出中的 `seq_alignment_status`: + - 若为 `SAFE`:表明 sequence 位置已超前于现有数据(例如已有试点导入推进了序列),可直接使用; + - 若为 `UNSAFE`:表明 sequence 当前位置落后或等于现有主键,**绝对禁止直接继续 Generator Import**! + 3. **Sequence 对齐专项步骤(需 DBA 确认并在维护窗口执行)**: + 针对落后的序列,使用 Kingbase 兼容的管理语法调整序列游标: + ```sql + -- 示例:对齐 gen_table sequence + SELECT setval('gen_table_table_id_seq', (SELECT COALESCE(MAX(table_id), 0) + 1 FROM gen_table), false); + -- 示例:对齐 gen_table_column sequence + SELECT setval('gen_table_column_column_id_seq', (SELECT COALESCE(MAX(column_id), 0) + 1 FROM gen_table_column), false); + ``` + 4. 重新执行 `003_generator_identity_verify.sql`,确认全部输出 `SAFE` 且最终结果为 `VERIFY: PASS` 后,方可开放表导入功能。 + +--- + +## 6. 执行命令示例(无真实凭据) + +```bash +# 步骤 1: 预检并确认 Scenario +ksql -h -p -U -d -f 001_generator_identity_precheck.sql + +# 步骤 2: 根据 Precheck 决策矩阵有选择地执行对应补丁 +# 仅当命中 Scenario A 或 D 时: +ksql -h -p -U -d -f 002a_gen_table_identity_patch.sql +# 仅当命中 Scenario A 或 C 时: +ksql -h -p -U -d -f 002b_gen_table_column_identity_patch.sql + +# 步骤 3: 严格复核验证 +ksql -h -p -U -d -f 003_generator_identity_verify.sql +``` diff --git a/docs/generator-v2/01_kingbase_import_identity_patch.md b/docs/generator-v2/01_kingbase_import_identity_patch.md new file mode 100644 index 000000000..9128eb646 --- /dev/null +++ b/docs/generator-v2/01_kingbase_import_identity_patch.md @@ -0,0 +1,202 @@ +# Generator 基础设施补丁 — Kingbase Import Identity 修复记录 + +本文档详细记录 RuoYi Generator 代码生成器在 KingbaseES 数据库环境下的核心运行时兼容性问题排查、补丁设计、安全加固与验证全过程。 + +--- + +## 1. 问题背景 + +RailwaySystem 后台基于 RuoYi-Vue 架构改造,数据库由 MySQL 迁移至国产 KingbaseES (V8R6 / PostgreSQL 语法体系)。代码生成器(`ruoyi-generator` 模块)用于读取数据库系统元数据、导入数据表配置并自动生成前端 Vue 与后端 Java/MyBatis 代码。 + +在 Generator 元数据体系中,包含两张核心表: +- `gen_table`:存储已导入代码生成的表元数据; +- `gen_table_column`:存储表关联的列元数据。 + +在最初从 MySQL 初始化表结构至 Kingbase 时,这两张表的自增主键定义未能正确映射为 Kingbase 标准的 Identity 列。 + +--- + +## 2. 第一次真实 Import 如何暴露问题 + +在推进首个业务模块试点开发(**Teacher Pilot**,教师主数据模块)期间,需要通过前端代码生成管理界面(`/tool/gen`)调用“导入表(Import Table)”功能将 `train_teacher` 表导入至 Generator 配置库中。 + +当点击导入并提交时,后端触发了 `GenTableServiceImpl.importGenTable` 调用。此时服务端直接抛出数据库异常,表导入操作失败,导致后续代码生成流程无法继续。 + +--- + +## 3. 错误现象 + +后端日志与数据库抛出类似如下的主键冲突与唯一约束冲突异常: + +```text +### Error updating database. Cause: com.kingbase8.util.KSQLException: ERROR: duplicate key value violates unique constraint "gen_table_pkey" + Detail: Key (table_id)=(0) already exists. +### The error may exist in URL [jar:file:.../ruoyi-generator.jar!/mapper/generator/GenTableMapper.xml] +### The error may involve com.ruoyi.generator.mapper.GenTableMapper.insertGenTable-Inline +### The error occurred while setting parameters +### SQL: insert into gen_table ( table_name, table_comment, class_name, ... ) values ( ?, ?, ?, ... ) +``` + +对于 `gen_table_column`,同样在批量插入字段元数据时出现所有字段的 `column_id` 均被赋值为默认值 `0`,从而连续抛出主键冲突。 + +--- + +## 4. 根因分析 + +1. **MyBatis 配置与运行时预期**: + `GenTableMapper.xml` 与 `GenTableColumnMapper.xml` 中的插入语句均配置了: + ```xml + + + ``` + RuoYi 的应用层期望数据库能够在插入记录时自动生成唯一递增主键,并通过 JDBC 驱动的 `getGeneratedKeys` 返回该主键值并回填至 Java 实体对象的 `tableId` / `columnId`。 + +2. **原始 MySQL 建表定义**: + 在 `RuoYi-Vue/sql/ry_20250522.sql` 中,原始表结构为: + ```sql + CREATE TABLE gen_table ( + table_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + ... + PRIMARY KEY (table_id) + ); + CREATE TABLE gen_table_column ( + column_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + ... + PRIMARY KEY (column_id) + ); + ``` + +3. **Kingbase 结构缺失 Identity / Sequence**: + 在早期导入 Kingbase 实例时,由于 KingbaseES 不支持 MySQL 方言的 `AUTO_INCREMENT` 关键字,该列在创建时仅退化为 `BIGINT NOT NULL`,未配置 `GENERATED BY DEFAULT AS IDENTITY`,也未创建对应的自增序列(Sequence)。 + 由于 INSERT 语句未指定主键值,在某些兼容模式下默认填入 `0`,第二次插入必然导致唯一主键冲突。 + +4. **定位结论**: + 本问题属于典型的 **Generator 运行时基础设施兼容性缺陷(Generator Runtime Compatibility Bug)**,与业务层 Database V2 数据模型无关。 + +--- + +## 5. 补丁文件拆分与核心 DDL + +为了避免局部补丁状态(Partial Patch)下执行发生冲突,补丁拆分为两个独立文件: + +### 5.1 `002a_gen_table_identity_patch.sql` +```sql +ALTER TABLE gen_table +ALTER COLUMN table_id +ADD GENERATED BY DEFAULT AS IDENTITY; +``` + +### 5.2 `002b_gen_table_column_identity_patch.sql` +```sql +ALTER TABLE gen_table_column +ALTER COLUMN column_id +ADD GENERATED BY DEFAULT AS IDENTITY; +``` + +执行后,KingbaseES 底层会自动创建并维护两个关联序列: +- `gen_table_table_id_seq` +- `gen_table_column_column_id_seq` + +--- + +## 6. Precheck(预检)与 Partial Patch 安全机制 + +### 6.1 Partial Patch Safety(局部补丁状态安全) +在实际运维过程中,数据库可能处于以下四种状态之一: +1. **Case 1 (`CASE_1_BOTH_NEED_PATCH`)**:两列均未配置 Identity; +2. **Case 2 (`CASE_2_BOTH_ALREADY_PATCHED`)**:两列均已配置 Identity; +3. **Case 3 (`CASE_3_ONLY_COLUMN_NEEDS_PATCH`)**:`table_id` 已修复,但 `column_id` 未修复; +4. **Case 4 (`CASE_4_ONLY_TABLE_NEEDS_PATCH`)**:`column_id` 已修复,但 `table_id` 未修复。 + +若在 Case 3 下全量执行整脚本,Kingbase 执行 `gen_table` ALTER 时将直接报错 `column "table_id" of relation "gen_table" is already an identity column` 并中断退出。 + +**解决方案**: +- `001_generator_identity_precheck.sql` 会直接输出全局 Case 判定; +- 补丁拆分为 `002a` 与 `002b`,根据 Precheck 的判定选择性执行,实现精准幂等与局部安全性。 + +--- + +## 7. Existing Metadata Safety 与 Sequence Alignment + +### 7.1 核心原则 +> **Identity 属性存在 ≠ Sequence 一定适合下一次 Insert** + +在执行 `ALTER TABLE ... ADD GENERATED BY DEFAULT AS IDENTITY` 时: +- **空表环境(0 rows)**:默认 Sequence `start_value = 1`,下一次插入分配 ID 为 1,天然安全; +- **已有数据环境(已存在历史元数据)**: + - Kingbase `ADD IDENTITY` 默认创建从 1 开始的 Sequence,**不宣称自动对齐已有数据的 MAX(id)**; + - 若直接执行 Insert,Sequence 将尝试从 1 开始分配,如果已有数据包含 `id = 1`,将再次引发主键冲突! + +### 7.2 Sequence 对齐机制(Sequence Alignment) +对于非空表环境,执行 Patch 后必须通过 Verify 脚本校验序列游标: +- 计算表达式:`next_id = (CASE WHEN is_called THEN last_value + 1 ELSE last_value END)` +- 若 `next_id > MAX(existing_id)`,判定为 `SAFE`; +- 若 `next_id <= MAX(existing_id)`,判定为 `UNSAFE`,必须由 DBA 执行专项对齐脚本: + ```sql + -- 同步 Sequence 游标至当前最大值之后 + SELECT setval('gen_table_table_id_seq', (SELECT COALESCE(MAX(table_id), 0) + 1 FROM gen_table), false); + SELECT setval('gen_table_column_column_id_seq', (SELECT COALESCE(MAX(column_id), 0) + 1 FROM gen_table_column), false); + ``` + +--- + +## 8. Verify(复核验证) + +执行只读复核脚本 `003_generator_identity_verify.sql`,执行四大维度的综合核验: +1. **核验列属性**:确认两列 `attidentity = 'd'`; +2. **核验 Sequence 存在性**:确认关联序列存在且 `relkind = 'S'`; +3. **核验依赖关系**:确认 `sys_depend.deptype = 'i'`(内部生命周期绑定); +4. **核验 Sequence 对齐安全性**:确认两表的 Sequence 处于 `SAFE` 或 `EMPTY_TABLE_SAFE` 状态; +5. **最终判定**:四项维度全通过输出 `VERIFY: PASS`。 + +--- + +## 9. Rollback 风险说明 + +若因特殊场景需要撤销该修改,语法为: +```sql +ALTER TABLE gen_table ALTER COLUMN table_id DROP IDENTITY IF EXISTS; +ALTER TABLE gen_table_column ALTER COLUMN column_id DROP IDENTITY IF EXISTS; +``` + +### 回滚风险评估 +1. **Generator 瘫痪**:一旦移除 Identity,Generator 将立即恢复至无法导入表的故障状态; +2. **元数据序列断裂**:若已有元数据记录,回滚后再配置可能造成序列重新错位; +3. **结论**:该补丁作为 RuoYi 适配 Kingbase 必需的基础设施配置,**严禁回滚**;任何环境初始化后均应持久化保留此配置。 + +--- + +## 10. 新环境部署要求 + +在搭建全新 Kingbase 部署环境或自动化 CI/CD 环境时: +1. 执行若依基础建表脚本; +2. 执行预检: + ```bash + ksql -h -p -U -d -f RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql + ``` +3. 按照决策矩阵执行对应补丁(空表全新环境直接依次执行 `002a` 和 `002b`); +4. 执行复核验证并确认输出 `VERIFY: PASS`: + ```bash + ksql -h -p -U -d -f RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql + ``` + +--- + +## 11. 与 Database V2 无关性声明 + +- **范围隔离**:本修复纯属 Generator 工具层基础设施兼容补丁,仅修改 `public.gen_table` 与 `public.gen_table_column` 两张系统级代码生成支撑表的主键属性; +- **业务无关**:零改动 21 张 `train_*` 业务模型表;零改动任何业务枚举、外键约束与数据字典; +- **版本独立**:补丁存放于 `RuoYi-Vue/sql/generator/kingbase/`,独立于业务迁移目录 `RuoYi-Vue/sql/train/v2/`。 + +--- + +## 12. Teacher Pilot 验证事实与当前共享库核验 + +- 在 Teacher Pilot(`feat/issue-01-01-teacher`)开发期间,共享 Kingbase 数据库已应用该 DDL 修复; +- 真实导入测试:调用 Generator 导入 `train_teacher` 表,`gen_table` 成功生成非零递增主键(`table_id = 1`),同时其 16 个字段成功写入 `gen_table_column`(`column_id = 1..16`); +- 代码生成测试:成功基于导入元数据生成教师主数据相关 Controller、Service、Mapper、Domain 及前端 Vue 页面,未产生任何主键冲突; +- **当前共享库只读核验数据(实测)**: + - `gen_table`: `row_count = 1`, `max_id = 1`, `sequence last_value = 2`, `is_called = true`,下一个分配值为 3 > 1,状态为 `SAFE`; + - `gen_table_column`: `row_count = 16`, `max_id = 16`, `sequence last_value = 32`, `is_called = true`,下一个分配值为 33 > 16,状态为 `SAFE`; + - 属性 `attidentity = 'd'`,序列依赖均为 `deptype = 'i'`; + - 复核脚本输出:`VERIFY: PASS (所有 Identity、Sequence、依赖关系与序列对齐安全核验一致通过)`。