Files
im_manager/docs/admin-adfahi-research/sql/IM-Server改造.sql
T

465 lines
21 KiB
SQL

-- IM Server schema changes for the GedaDa management-console adaptation.
-- Target: MySQL 8.0+, one independent tenant business database.
-- Default local tenant database: `tinode`.
-- IMPORTANT: execute this migration once for every tenant database. Tenant
-- business tables intentionally do not contain tenant_id.
-- Review before execution. DDL statements auto-commit in MySQL.
SET NAMES utf8mb4;
USE `tinode`;
-- ============================================================
-- 0. User account number.
-- account_no is the short user number shown in Admin. The real primary key
-- im_users.id remains the internal Tinode/Snowflake user ID.
-- Existing users are backfilled from 1000 by created time; new users are
-- assigned by IM Server in the user creation transaction.
-- ============================================================
SET @sql := IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_users' AND COLUMN_NAME = 'account_no') = 0,
'ALTER TABLE `im_users` ADD COLUMN `account_no` int unsigned NOT NULL DEFAULT 0 COMMENT ''用户编号'' AFTER `id`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @im_user_account_no := (
SELECT GREATEST(COALESCE(MAX(`account_no`), 999), 999)
FROM `im_users`
WHERE `account_no` > 0
);
UPDATE `im_users` AS u
JOIN (
SELECT ordered.`id`, (@im_user_account_no := @im_user_account_no + 1) AS next_no
FROM (
SELECT `id`
FROM `im_users`
WHERE `account_no` = 0
ORDER BY `createdat`, `id`
) AS ordered
) AS seq ON seq.`id` = u.`id`
SET u.`account_no` = seq.next_no
WHERE u.`account_no` = 0;
SET @sql := IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_users' AND INDEX_NAME = 'uk_im_users_account_no') = 0,
'CREATE UNIQUE INDEX `uk_im_users_account_no` ON `im_users` (`account_no`)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ============================================================
-- 1. Applied runtime policy snapshots.
-- policy_type: security, user_policy_groups, sensitive_words.
-- The application uses the latest row with apply_status=applied and also keeps
-- an in-memory cache. This table makes restart and Kafka replay deterministic.
-- ============================================================
CREATE TABLE IF NOT EXISTS `im_runtime_policy_snapshots` (
`id` bigint NOT NULL AUTO_INCREMENT,
`policy_type` varchar(32) NOT NULL,
`policy_version` bigint unsigned NOT NULL,
`payload` json NOT NULL,
`payload_sha256` char(64) NOT NULL,
`source_event_id` varchar(96) DEFAULT NULL COMMENT 'NULL for a manual bootstrap snapshot',
`apply_status` varchar(16) NOT NULL DEFAULT 'applied' COMMENT 'applied or failed',
`apply_error` varchar(1000) NOT NULL DEFAULT '',
`applied_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_im_runtime_policy_version` (`policy_type`, `policy_version`),
UNIQUE KEY `uk_im_runtime_policy_event` (`source_event_id`),
KEY `idx_im_runtime_policy_latest` (`policy_type`, `apply_status`, `policy_version` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Last applied Admin policy payloads';
-- ============================================================
-- 2. User login history and login failure history.
-- Never store plaintext passwords, verification codes, access tokens or refresh
-- tokens. account_hash is SHA-256 of the normalized login account; account_mask
-- is the value shown in Admin.
-- login_type: password, sms_code, sso, qr_code, token_refresh.
-- event_type: login, refresh, logout, kicked, session_expired.
-- failure_type: password_error, code_error, account_not_found,
-- account_disabled, rate_limited, risk_blocked.
-- ============================================================
CREATE TABLE IF NOT EXISTS `im_user_login_logs` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`username` varchar(64) NOT NULL DEFAULT '',
`account_type` varchar(24) NOT NULL DEFAULT '',
`account_hash` char(64) NOT NULL DEFAULT '',
`account_mask` varchar(128) NOT NULL DEFAULT '',
`login_type` varchar(24) NOT NULL,
`event_type` varchar(24) NOT NULL DEFAULT 'login',
`session_id` varchar(96) NOT NULL DEFAULT '' COMMENT 'Opaque session identifier, never an auth token',
`device_id` varchar(255) NOT NULL DEFAULT '',
`platform` varchar(32) NOT NULL DEFAULT 'unknown',
`app_version` varchar(32) NOT NULL DEFAULT '',
`ip` varchar(64) NOT NULL DEFAULT '',
`region` varchar(128) NOT NULL DEFAULT '',
`user_agent` varchar(512) NOT NULL DEFAULT '',
`risk_level` varchar(16) NOT NULL DEFAULT 'low',
`request_id` varchar(64) NOT NULL DEFAULT '',
`login_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`logout_at` datetime(3) DEFAULT NULL,
`metadata` json DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
KEY `idx_im_user_login_user_time` (`user_id`, `login_at` DESC, `id` DESC),
KEY `idx_im_user_login_ip_time` (`ip`, `login_at` DESC, `id` DESC),
KEY `idx_im_user_login_device_time` (`device_id`(64), `login_at` DESC),
KEY `idx_im_user_login_request` (`request_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='User login and session event history';
CREATE TABLE IF NOT EXISTS `im_login_failure_logs` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL COMMENT 'NULL when the account cannot be resolved',
`account_type` varchar(24) NOT NULL DEFAULT '',
`account_hash` char(64) NOT NULL DEFAULT '',
`account_mask` varchar(128) NOT NULL DEFAULT '',
`failure_type` varchar(32) NOT NULL,
`reason` varchar(96) NOT NULL DEFAULT '' COMMENT 'Stable backend reason code',
`detail` varchar(500) NOT NULL DEFAULT '' COMMENT 'Sanitized detail without credentials',
`ip` varchar(64) NOT NULL DEFAULT '',
`region` varchar(128) NOT NULL DEFAULT '',
`device_id` varchar(255) NOT NULL DEFAULT '',
`platform` varchar(32) NOT NULL DEFAULT 'unknown',
`app_version` varchar(32) NOT NULL DEFAULT '',
`user_agent` varchar(512) NOT NULL DEFAULT '',
`risk_level` varchar(16) NOT NULL DEFAULT 'medium',
`risk_action` varchar(24) NOT NULL DEFAULT 'audit_only',
`request_id` varchar(64) NOT NULL DEFAULT '',
`attempt_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`metadata` json DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
KEY `idx_im_login_failure_account_time` (`account_hash`, `attempt_at` DESC, `id` DESC),
KEY `idx_im_login_failure_user_time` (`user_id`, `attempt_at` DESC, `id` DESC),
KEY `idx_im_login_failure_ip_time` (`ip`, `attempt_at` DESC, `id` DESC),
KEY `idx_im_login_failure_type_time` (`failure_type`, `attempt_at` DESC, `id` DESC),
KEY `idx_im_login_failure_request` (`request_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Authentication failure and risk-block history';
-- ============================================================
-- 3. Message administration actions.
-- Messages remain in im_messages/im_group_messages. This table is an immutable
-- action overlay so hide/delete can be audited and restored.
-- conversation_type: private, group.
-- action: hide, unhide, delete, restore.
-- action_status: pending, success, failed, dead.
-- ============================================================
CREATE TABLE IF NOT EXISTS `im_message_admin_actions` (
`id` bigint NOT NULL AUTO_INCREMENT,
`action_id` varchar(96) NOT NULL,
`command_id` varchar(96) NOT NULL,
`conversation_type` varchar(16) NOT NULL,
`topic` varchar(64) NOT NULL,
`seqid` bigint NOT NULL,
`sender_user_id` bigint DEFAULT NULL,
`action` varchar(16) NOT NULL,
`action_status` varchar(16) NOT NULL DEFAULT 'pending',
`reason` varchar(500) NOT NULL DEFAULT '',
`actor_admin_id` bigint NOT NULL DEFAULT 0,
`request_id` varchar(64) NOT NULL DEFAULT '',
`before_snapshot` json DEFAULT NULL,
`after_snapshot` json DEFAULT NULL,
`failure_reason` varchar(96) NOT NULL DEFAULT '',
`failure_message` varchar(1000) NOT NULL DEFAULT '',
`processed_at` datetime(3) DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_im_message_admin_action_id` (`action_id`),
UNIQUE KEY `uk_im_message_admin_command` (`command_id`),
KEY `idx_im_message_admin_target` (`conversation_type`, `topic`, `seqid`, `id` DESC),
KEY `idx_im_message_admin_status` (`action_status`, `created_at`, `id`),
KEY `idx_im_message_admin_actor` (`actor_admin_id`, `created_at` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Auditable Admin message actions';
-- ============================================================
-- 4. Sensitive-word hits and policy decisions.
-- Store only a digest and a short masked excerpt. Do not duplicate complete
-- private messages in log tables.
-- policy decision reasons include FRIEND_DISABLED and MESSAGE_CURFEW_ACTIVE.
-- ============================================================
CREATE TABLE IF NOT EXISTS `im_sensitive_word_hits` (
`id` bigint NOT NULL AUTO_INCREMENT,
`word_id` bigint NOT NULL DEFAULT 0 COMMENT 'Admin control-database word ID',
`word_group_id` bigint NOT NULL DEFAULT 0,
`word_snapshot` varchar(255) NOT NULL DEFAULT '' COMMENT 'Masked word snapshot',
`scene` varchar(32) NOT NULL,
`user_id` bigint NOT NULL DEFAULT 0,
`target_user_id` bigint DEFAULT NULL,
`topic` varchar(64) NOT NULL DEFAULT '',
`seqid` bigint DEFAULT NULL,
`content_sha256` char(64) NOT NULL DEFAULT '',
`matched_excerpt` varchar(500) NOT NULL DEFAULT '',
`match_type` varchar(24) NOT NULL DEFAULT 'contains',
`action` varchar(24) NOT NULL,
`risk_level` varchar(16) NOT NULL DEFAULT 'medium',
`request_id` varchar(64) NOT NULL DEFAULT '',
`metadata` json DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
KEY `idx_im_sensitive_hit_scene_time` (`scene`, `created_at` DESC, `id` DESC),
KEY `idx_im_sensitive_hit_user_time` (`user_id`, `created_at` DESC, `id` DESC),
KEY `idx_im_sensitive_hit_target` (`topic`, `seqid`, `id` DESC),
KEY `idx_im_sensitive_hit_word` (`word_id`, `created_at` DESC),
KEY `idx_im_sensitive_hit_request` (`request_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Sensitive-word match events';
CREATE TABLE IF NOT EXISTS `im_policy_decision_logs` (
`id` bigint NOT NULL AUTO_INCREMENT,
`policy_type` varchar(32) NOT NULL COMMENT 'security or user_policy_group',
`policy_version` bigint unsigned NOT NULL DEFAULT 0,
`policy_group_id` bigint NOT NULL DEFAULT 0,
`scene` varchar(64) NOT NULL COMMENT 'friend_request, qrcode_resolve, private_message and so on',
`action` varchar(24) NOT NULL COMMENT 'deny, challenge or audit_only',
`reason_code` varchar(96) NOT NULL COMMENT 'For example FRIEND_DISABLED or MESSAGE_CURFEW_ACTIVE',
`user_id` bigint NOT NULL DEFAULT 0,
`target_user_id` bigint DEFAULT NULL,
`topic` varchar(64) NOT NULL DEFAULT '',
`request_id` varchar(64) NOT NULL DEFAULT '',
`ip` varchar(64) NOT NULL DEFAULT '',
`metadata` json DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
KEY `idx_im_policy_decision_user_time` (`user_id`, `created_at` DESC, `id` DESC),
KEY `idx_im_policy_decision_reason_time` (`reason_code`, `created_at` DESC, `id` DESC),
KEY `idx_im_policy_decision_scene_time` (`scene`, `created_at` DESC, `id` DESC),
KEY `idx_im_policy_decision_request` (`request_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Runtime policy deny/challenge decisions';
-- ============================================================
-- 5. Group event history and daily IP/account statistics.
-- im_group_events_outbox remains the delivery table; this log is the queryable
-- business history after the operation result is known.
-- ============================================================
CREATE TABLE IF NOT EXISTS `im_group_event_logs` (
`id` bigint NOT NULL AUTO_INCREMENT,
`event_id` varchar(96) NOT NULL,
`topic` varchar(64) NOT NULL,
`group_id` bigint DEFAULT NULL,
`event_type` varchar(64) NOT NULL,
`actor_user_id` bigint NOT NULL DEFAULT 0,
`target_user_id` bigint DEFAULT NULL,
`result` varchar(16) NOT NULL DEFAULT 'success',
`reason` varchar(500) NOT NULL DEFAULT '',
`request_id` varchar(64) NOT NULL DEFAULT '',
`before_snapshot` json DEFAULT NULL,
`after_snapshot` json DEFAULT NULL,
`metadata` json DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_im_group_event_log_event` (`event_id`),
KEY `idx_im_group_event_topic_time` (`topic`, `created_at` DESC, `id` DESC),
KEY `idx_im_group_event_type_time` (`event_type`, `created_at` DESC, `id` DESC),
KEY `idx_im_group_event_actor_time` (`actor_user_id`, `created_at` DESC, `id` DESC),
KEY `idx_im_group_event_request` (`request_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Queryable group event audit history';
CREATE TABLE IF NOT EXISTS `im_ip_account_stats_daily` (
`id` bigint NOT NULL AUTO_INCREMENT,
`stat_date` date NOT NULL,
`ip` varchar(64) NOT NULL,
`region` varchar(128) NOT NULL DEFAULT '',
`account_count` int unsigned NOT NULL DEFAULT 0,
`user_count` int unsigned NOT NULL DEFAULT 0,
`login_success_count` int unsigned NOT NULL DEFAULT 0,
`login_failure_count` int unsigned NOT NULL DEFAULT 0,
`first_seen_at` datetime(3) DEFAULT NULL,
`last_seen_at` datetime(3) DEFAULT NULL,
`risk_level` varchar(16) NOT NULL DEFAULT 'low',
`risk_action` varchar(24) NOT NULL DEFAULT 'audit_only',
`sample_user_ids` json DEFAULT NULL COMMENT 'Bounded sample only, not an unlimited array',
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_im_ip_account_stats_day_ip` (`stat_date`, `ip`),
KEY `idx_im_ip_account_stats_ip_day` (`ip`, `stat_date` DESC),
KEY `idx_im_ip_account_stats_risk_day` (`risk_level`, `stat_date` DESC, `id` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Daily IP and account aggregation';
-- ============================================================
-- 6. Compatible enhancements to existing tables.
-- Every ALTER is guarded by information_schema and can be rerun.
-- ============================================================
-- Message management queries need topic/time and sender/time indexes.
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_messages'
AND INDEX_NAME = 'idx_im_messages_topic_created') = 0,
'ALTER TABLE `im_messages` ADD INDEX `idx_im_messages_topic_created` (`topic`, `createdat`, `id`)',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_messages'
AND INDEX_NAME = 'idx_im_messages_from_created') = 0,
'ALTER TABLE `im_messages` ADD INDEX `idx_im_messages_from_created` (`from`, `createdat`, `id`)',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
-- Extend the existing Tinode device record for device and login-history views.
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
AND COLUMN_NAME = 'firstseen') = 0,
'ALTER TABLE `im_devices` ADD COLUMN `firstseen` DATETIME(3) DEFAULT NULL AFTER `platform`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
AND COLUMN_NAME = 'lastip') = 0,
'ALTER TABLE `im_devices` ADD COLUMN `lastip` VARCHAR(64) NOT NULL DEFAULT '''' AFTER `lastseen`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
AND COLUMN_NAME = 'useragent') = 0,
'ALTER TABLE `im_devices` ADD COLUMN `useragent` VARCHAR(512) NOT NULL DEFAULT '''' AFTER `lastip`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
AND COLUMN_NAME = 'appversion') = 0,
'ALTER TABLE `im_devices` ADD COLUMN `appversion` VARCHAR(32) NOT NULL DEFAULT '''' AFTER `useragent`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
AND COLUMN_NAME = 'status') = 0,
'ALTER TABLE `im_devices` ADD COLUMN `status` VARCHAR(16) NOT NULL DEFAULT ''active'' AFTER `appversion`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
-- Feedback handling fields required by the Admin processing workflow.
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
AND COLUMN_NAME = 'assigned_to') = 0,
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `assigned_to` BIGINT DEFAULT NULL AFTER `status`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
AND COLUMN_NAME = 'handled_by') = 0,
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `handled_by` BIGINT DEFAULT NULL AFTER `assigned_to`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
AND COLUMN_NAME = 'handled_at') = 0,
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `handled_at` DATETIME(3) DEFAULT NULL AFTER `handled_by`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
AND COLUMN_NAME = 'resolution') = 0,
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `resolution` VARCHAR(1000) NOT NULL DEFAULT '''' AFTER `handled_at`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
AND COLUMN_NAME = 'admin_remark') = 0,
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `admin_remark` VARCHAR(1000) NOT NULL DEFAULT '''' AFTER `resolution`',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
AND INDEX_NAME = 'idx_im_feedback_status_time') = 0,
'ALTER TABLE `im_feedback_submissions` ADD INDEX `idx_im_feedback_status_time` (`status`, `created_at`, `id`)',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
SET @im_sql = IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_audit_logs'
AND INDEX_NAME = 'idx_im_audit_logs_ip_time') = 0,
'ALTER TABLE `im_audit_logs` ADD INDEX `idx_im_audit_logs_ip_time` (`ip`, `created_at`, `id`)',
'SELECT 1'
);
PREPARE im_stmt FROM @im_sql;
EXECUTE im_stmt;
DEALLOCATE PREPARE im_stmt;
-- Post-deployment checks (read-only):
-- SELECT policy_type, policy_version, apply_status, applied_at
-- FROM im_runtime_policy_snapshots ORDER BY policy_type, policy_version DESC;
-- SELECT reason_code, scene, COUNT(*) AS deny_count
-- FROM im_policy_decision_logs
-- WHERE created_at >= CURRENT_DATE
-- GROUP BY reason_code, scene;
-- SELECT stat_date, ip, account_count, login_failure_count, risk_level
-- FROM im_ip_account_stats_daily ORDER BY stat_date DESC, login_failure_count DESC;