合并代码生成:集成Generator Identity基础设施修复
This commit is contained in:
commit
fe7e3c91ce
6 files changed
+562
No files matched your search
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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`<br>(两列均缺失 Identity) | 1. 执行 `002a_gen_table_identity_patch.sql`<br>2. 执行 `002b_gen_table_column_identity_patch.sql`<br>3. 执行 `003_generator_identity_verify.sql` |
|
||||
| **Scenario B** | `CASE_2_BOTH_ALREADY_PATCHED`<br>(两列均已配置 Identity) | **严禁执行任何 Patch!**<br>直接执行 `003_generator_identity_verify.sql` 复核。 |
|
||||
| **Scenario C** | `CASE_3_ONLY_COLUMN_NEEDS_PATCH`<br>(仅 `column_id` 缺失) | **严禁执行 002a!**<br>1. 仅执行 `002b_gen_table_column_identity_patch.sql`<br>2. 执行 `003_generator_identity_verify.sql` |
|
||||
| **Scenario D** | `CASE_4_ONLY_TABLE_NEEDS_PATCH`<br>(仅 `table_id` 缺失) | **严禁执行 002b!**<br>1. 仅执行 `002a_gen_table_identity_patch.sql`<br>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 <HOST> -p <PORT> -U <USER> -d <DBNAME> -f 001_generator_identity_precheck.sql
|
||||
|
||||
# 步骤 2: 根据 Precheck 决策矩阵有选择地执行对应补丁
|
||||
# 仅当命中 Scenario A 或 D 时:
|
||||
ksql -h <HOST> -p <PORT> -U <USER> -d <DBNAME> -f 002a_gen_table_identity_patch.sql
|
||||
# 仅当命中 Scenario A 或 C 时:
|
||||
ksql -h <HOST> -p <PORT> -U <USER> -d <DBNAME> -f 002b_gen_table_column_identity_patch.sql
|
||||
|
||||
# 步骤 3: 严格复核验证
|
||||
ksql -h <HOST> -p <PORT> -U <USER> -d <DBNAME> -f 003_generator_identity_verify.sql
|
||||
```
|
||||
@@ -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
|
||||
<insert id="insertGenTable" parameterType="GenTable" useGeneratedKeys="true" keyProperty="tableId">
|
||||
<insert id="insertGenTableColumn" parameterType="GenTableColumn" useGeneratedKeys="true" keyProperty="columnId">
|
||||
```
|
||||
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 <HOST> -p <PORT> -U <USER> -d <DBNAME> -f RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql
|
||||
```
|
||||
3. 按照决策矩阵执行对应补丁(空表全新环境直接依次执行 `002a` 和 `002b`);
|
||||
4. 执行复核验证并确认输出 `VERIFY: PASS`:
|
||||
```bash
|
||||
ksql -h <HOST> -p <PORT> -U <USER> -d <DBNAME> -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、依赖关系与序列对齐安全核验一致通过)`。
|
||||
Reference in new issue
Block a user