225 lines
11 KiB
SQL
225 lines
11 KiB
SQL
-- ==============================================================================
|
|
-- RailwaySystem QA Bootstrap: 06_verify_schema_integrity.sql
|
|
-- 目标数据库: railwaysystem_fusion_qa_20261013
|
|
-- 目标: 全自动化只读验证 Schema 结构完整性、字段存在性、部分唯一索引与跨模式业务关联
|
|
-- 安全性: 100% 只读断言,绝无任何 DDL/DML 写操作
|
|
-- ==============================================================================
|
|
|
|
DO $$
|
|
DECLARE
|
|
v_dbname VARCHAR;
|
|
v_cnt INT;
|
|
v_missing_tables TEXT := '';
|
|
v_table_name TEXT;
|
|
|
|
-- 23 张培训业务表全量清单 (严格与源码实体/Mapper及02脚本对齐)
|
|
v_train_tables TEXT[] := ARRAY[
|
|
'train_teacher', 'train_student', 'train_course',
|
|
'train_classroom_info', 'train_training_site', 'train_electronic_board',
|
|
'train_dormitory_info', 'train_class_plan', 'train_resource',
|
|
'train_course_resource', 'train_plan_course', 'train_enrollment',
|
|
'train_accommodation', 'train_lesson', 'train_score_item',
|
|
'train_student_score', 'train_dorm_auth', 'train_class_report',
|
|
'train_student_checkin', 'train_study_duration', 'train_student_portrait',
|
|
'train_external_resource_mapping', 'train_external_operation'
|
|
];
|
|
|
|
-- 9 张 IoT 智能门锁业务表清单
|
|
v_iot_tables TEXT[] := ARRAY[
|
|
'lock_group', 'lock_gateway', 'lock_device',
|
|
'lock_face', 'lock_auth', 'lock_access_log',
|
|
'lock_command', 'lock_mqtt_log', 'lock_service_config'
|
|
];
|
|
BEGIN
|
|
-- 1. 白名单断言: 校验当前数据库名称
|
|
SELECT current_database() INTO v_dbname;
|
|
IF v_dbname <> 'railwaysystem_fusion_qa_20261013' THEN
|
|
RAISE EXCEPTION '>>> [FATAL-SECURITY] 当前数据库名为 [%], 绝非授权的目标库 railwaysystem_fusion_qa_20261013! 中止验证!', v_dbname;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] 数据库白名单校验通过: %', v_dbname;
|
|
|
|
-- 2. 校验 public 模式下的 23 张培训业务表
|
|
FOREACH v_table_name IN ARRAY v_train_tables
|
|
LOOP
|
|
SELECT count(*) INTO v_cnt FROM information_schema.tables
|
|
WHERE table_schema = 'public' AND table_name = v_table_name;
|
|
IF v_cnt = 0 THEN
|
|
v_missing_tables := v_missing_tables || ' public.' || v_table_name;
|
|
END IF;
|
|
END LOOP;
|
|
|
|
IF v_missing_tables <> '' THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 缺少以下培训表: %', v_missing_tables;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] public 模式下的 23 张培训业务表全部物理齐备';
|
|
|
|
-- 3. 校验 iotlocks 模式下的 9 张物联核心表
|
|
v_missing_tables := '';
|
|
FOREACH v_table_name IN ARRAY v_iot_tables
|
|
LOOP
|
|
SELECT count(*) INTO v_cnt FROM information_schema.tables
|
|
WHERE table_schema = 'iotlocks' AND table_name = v_table_name;
|
|
IF v_cnt = 0 THEN
|
|
v_missing_tables := v_missing_tables || ' iotlocks.' || v_table_name;
|
|
END IF;
|
|
END LOOP;
|
|
|
|
IF v_missing_tables <> '' THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 缺少以下 IoT 表: %', v_missing_tables;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] iotlocks 模式下的 9 张物联业务表全部物理齐备';
|
|
|
|
-- 4. 关键业务列与 Mapper 匹配列物理存在性核验 (严格断言)
|
|
-- 4.1 public.train_student.person_no
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_student' AND column_name = 'person_no';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_student.person_no 缺失!';
|
|
END IF;
|
|
|
|
-- 4.2 public.train_classroom_info.class_room_id
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_classroom_info' AND column_name = 'class_room_id';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_classroom_info.class_room_id 缺失!';
|
|
END IF;
|
|
|
|
-- 4.3 public.train_resource.resource_id
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_resource' AND column_name = 'resource_id';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_resource.resource_id 缺失!';
|
|
END IF;
|
|
|
|
-- 4.4 public.train_enrollment.student_id
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_enrollment' AND column_name = 'student_id';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_enrollment.student_id 缺失!';
|
|
END IF;
|
|
|
|
-- 4.5 public.train_score_item.weight
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_score_item' AND column_name = 'weight';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_score_item.weight 缺失!';
|
|
END IF;
|
|
|
|
-- 4.6 public.train_accommodation.accommodation_status 与 accommodation_id
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_accommodation' AND column_name = 'accommodation_status';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_accommodation.accommodation_status 缺失!';
|
|
END IF;
|
|
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_accommodation' AND column_name = 'accommodation_id';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_accommodation.accommodation_id 缺失!';
|
|
END IF;
|
|
|
|
-- 4.7 public.train_external_operation.door_auth_id 与 business_type
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_external_operation' AND column_name = 'door_auth_id';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_external_operation.door_auth_id 缺失!';
|
|
END IF;
|
|
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'train_external_operation' AND column_name = 'business_type';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 public.train_external_operation.business_type 缺失!';
|
|
END IF;
|
|
|
|
-- 4.8 iotlocks 核心字段
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'iotlocks' AND table_name = 'lock_device' AND column_name = 'device_sn';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 iotlocks.lock_device.device_sn 缺失!';
|
|
END IF;
|
|
|
|
SELECT count(*) INTO v_cnt FROM information_schema.columns
|
|
WHERE table_schema = 'iotlocks' AND table_name = 'lock_auth' AND column_name = 'person_no';
|
|
IF v_cnt = 0 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-P0] 字段 iotlocks.lock_auth.person_no 缺失!';
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] 关键业务对接字段与 Mapper 真实列物理存在性核验全部通过';
|
|
|
|
-- 5. 跨模式业务关联完整性断言
|
|
-- 5.1 门锁 SN 关联一致性: train_dormitory_info 与 lock_device
|
|
SELECT count(*) INTO v_cnt
|
|
FROM public.train_dormitory_info d
|
|
JOIN iotlocks.lock_device ld ON d.lock_device_sn = ld.device_sn
|
|
WHERE d.lock_device_sn IN ('LOCK-ZY-101', 'LOCK-ZY-102');
|
|
IF v_cnt < 2 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 宿舍 lock_device_sn 与 IoT 门锁 device_sn 跨模式匹配失败: 匹配数 % < 2', v_cnt;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] 宿舍与智能门锁跨模式设备匹配校验通过 (有效匹配数: %)', v_cnt;
|
|
|
|
-- 5.2 人员编号关联一致性: train_student.person_no 与 lock_auth.person_no
|
|
SELECT count(DISTINCT s.person_no) INTO v_cnt
|
|
FROM public.train_student s
|
|
JOIN iotlocks.lock_auth la ON s.person_no = la.person_no
|
|
WHERE s.person_no IN ('AA000001', 'AA000002');
|
|
IF v_cnt < 2 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 学员 person_no 与 IoT 授权 lock_auth.person_no 关联失败: 匹配学员数 % < 2', v_cnt;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] 学员主档与 IoT 门锁授权跨模式人员匹配校验通过 (关联人员数: %)', v_cnt;
|
|
|
|
-- 5.3 住宿生命周期覆盖性核验: 入住、退宿、重新入住
|
|
-- 当前在住记录 (accommodation_status = 1)
|
|
SELECT count(*) INTO v_cnt
|
|
FROM public.train_accommodation
|
|
WHERE accommodation_status = 1 AND enrollment_id IN (2001, 2002);
|
|
IF v_cnt < 2 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 缺少当前在住状态记录, 实际检测到 %', v_cnt;
|
|
END IF;
|
|
|
|
-- 历史退宿记录 (accommodation_status = 2)
|
|
SELECT count(*) INTO v_cnt
|
|
FROM public.train_accommodation
|
|
WHERE accommodation_status = 2 AND enrollment_id = 2001;
|
|
IF v_cnt < 1 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 缺少学员 1 历史退宿状态记录, 实际检测到 %', v_cnt;
|
|
END IF;
|
|
|
|
-- 重新入住验证: 学员 1 (enrollment_id = 2001) 同时存在历史退宿(2)与当前在住(1)记录
|
|
SELECT count(*) INTO v_cnt
|
|
FROM public.train_accommodation
|
|
WHERE enrollment_id = 2001;
|
|
IF v_cnt < 2 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 学员 1 未能同时存在退宿与在住记录 (重新入住多状态受阻), 实际检测到 %', v_cnt;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] 住宿生命周期与重新入住结构验证通过 (退宿与在住共存通过部分唯一索引放行)';
|
|
|
|
-- 5.4 【任务6核心门禁】端到端测试初始未授权状态断言 (确保未预置成功状态冒充完整链路)
|
|
SELECT count(*) INTO v_cnt
|
|
FROM iotlocks.lock_auth
|
|
WHERE sync_status = '0' AND person_no IN ('AA000001', 'AA000002');
|
|
IF v_cnt < 2 THEN
|
|
RAISE EXCEPTION '>>> [FAIL-SECURITY] 融合测试门锁授权记录未处于初始未授权状态(sync_status=0), 存在伪造成功状态风险: 实际检测到 %', v_cnt;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] 初始门锁授权数据均为待下发状态 (sync_status=0),完全符合端到端真实下发推导规范';
|
|
|
|
-- 6. 基础测试数据规模合规性检查
|
|
SELECT count(*) INTO v_cnt FROM public.train_student;
|
|
IF v_cnt < 35 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 学员合成数据行数异常: 期望 >= 35, 实际检测到 %', v_cnt;
|
|
END IF;
|
|
|
|
SELECT count(*) INTO v_cnt FROM public.train_teacher;
|
|
IF v_cnt < 6 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 教师合成数据行数异常: 期望 >= 6, 实际检测到 %', v_cnt;
|
|
END IF;
|
|
|
|
SELECT count(*) INTO v_cnt FROM public.train_dormitory_info;
|
|
IF v_cnt < 20 THEN
|
|
RAISE EXCEPTION '>>> [FAIL] 宿舍合成数据行数异常: 期望 >= 20, 实际检测到 %', v_cnt;
|
|
END IF;
|
|
RAISE NOTICE '>>> [PASS] 纯合成基础测试数据行数校验全部达标 (学员 35+, 教师 6+, 宿舍 20+)';
|
|
|
|
RAISE NOTICE '=========================================================';
|
|
RAISE NOTICE '>>> [ALL-PASS] 数据库 Schema、字段断言与跨表业务关联检验全部通过! <<<';
|
|
RAISE NOTICE '=========================================================';
|
|
END $$;
|