-- ============================================================================== -- 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 $$;