Merge pull request #65 from duckietm/dev

🆙 Small update to SQL
This commit is contained in:
DuckieTM
2026-04-03 19:11:22 +02:00
committed by GitHub
+20 -595
View File
@@ -1,148 +1,20 @@
-- =============================================================================
-- Gameserver Database Optimization Migration
-- =============================================================================
-- This migration optimizes the gameserver tables (not website_* tables).
--
-- IMPORTANT: This script is designed to run on a POPULATED database safely.
-- It uses IF NOT EXISTS / IF EXISTS where possible.
--
-- What it does:
-- 1. Converts Aria/MyISAM tables to InnoDB (required for foreign keys)
-- 2. Fixes data type mismatches (unsigned/signed) so FKs can be created
-- 3. Adds missing primary keys and indexes
-- 4. Adds foreign key constraints for referential integrity
--
-- BEFORE RUNNING:
-- - Back up your database!
-- - Run on a test environment first
-- - The script disables FK checks during migration to avoid ordering issues
-- =============================================================================
SET FOREIGN_KEY_CHECKS = 0;
SET @OLD_SQL_MODE = @@SQL_MODE;
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
-- =============================================================================
-- PHASE 1: Convert storage engines to InnoDB, ROW_FORMAT to DYNAMIC
-- =============================================================================
-- Foreign keys require InnoDB. Converting Aria and MyISAM tables.
-- InnoDB does not support ROW_FORMAT=FIXED, so all tables get ROW_FORMAT=DYNAMIC.
-- Note: Aria tables lose PAGE_CHECKSUM (InnoDB has its own checksumming).
-- Core emulator tables
ALTER TABLE IF EXISTS `achievements` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `bot_serves` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_clothing` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_club_offers` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_featured_pages` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_items_limited` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `chatlogs_private` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `chatlogs_room` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `commandlogs` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `emulator_errors` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
-- Items & marketplace
ALTER TABLE IF EXISTS `items` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `items_crackable` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `items_hoppers` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `items_presents` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `items_teleports` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `marketplace_items` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
-- Navigator & rooms
ALTER TABLE IF EXISTS `navigator_publiccats` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `navigator_publics` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `navigator_filter` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `navigator_flatcats` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `nux_gifts` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `rooms` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_bans` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_enter_log` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_game_scores` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_models` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_models_custom` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_mutes` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_promotions` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_rights` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_votes` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `room_wordfilter` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
-- Pets
ALTER TABLE IF EXISTS `pet_breeding` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `pet_breeding_races` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `pet_breeds` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `pet_drinks` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `pet_foods` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `pet_items` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
-- Polls
ALTER TABLE IF EXISTS `polls` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `polls_answers` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `polls_questions` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
-- Users
ALTER TABLE IF EXISTS `users_achievements` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_achievements_queue` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_clothing` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_currency` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_effects` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_favorite_rooms` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_navigator_settings` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_pets` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_recipes` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `user_window_settings` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
-- Misc
ALTER TABLE IF EXISTS `crafting_altars_recipes` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `crafting_recipes` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `crafting_recipes_ingredients` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `namechange_log` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `recycler_prizes` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `special_enables` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `vouchers` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_items` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_pages` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `navigator_flatcats` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `wordfilter` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `wired_rewards_given` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `youtube_playlists` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `bots` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `logs_shop_purchases` MODIFY `user_id` int(11) DEFAULT NULL;
ALTER TABLE IF EXISTS `users_subscriptions` MODIFY `user_id` int(11) DEFAULT NULL;
ALTER TABLE IF EXISTS `items` MODIFY `item_id` int(11) unsigned DEFAULT 0;
-- =============================================================================
-- PHASE 2: Fix data type mismatches (unsigned vs signed)
-- =============================================================================
-- Foreign keys require EXACT type matches including signedness.
-- Fix columns where referenced PK and referencing FK differ.
-- 2a. users.id is int(11) SIGNED → fix unsigned user_id columns
ALTER TABLE IF EXISTS `logs_hc_payday`
MODIFY `user_id` int(11) DEFAULT NULL;
ALTER TABLE IF EXISTS `logs_shop_purchases`
MODIFY `user_id` int(11) DEFAULT NULL;
ALTER TABLE IF EXISTS `users_subscriptions`
MODIFY `user_id` int(11) DEFAULT NULL;
-- 2b. items_base.id is int(11) UNSIGNED → fix signed FK columns to match
ALTER TABLE IF EXISTS `items`
MODIFY `item_id` int(11) unsigned DEFAULT 0;
ALTER TABLE IF EXISTS `economy_furniture`
MODIFY `items_base_id` int(11) unsigned NOT NULL;
ALTER TABLE IF EXISTS `items_crackable`
MODIFY `item_id` int(11) unsigned NOT NULL;
-- 2c. guilds_forums_threads.id is int(10) UNSIGNED → fix signed FK columns
ALTER TABLE IF EXISTS `guilds_forums_comments`
MODIFY `thread_id` int(10) unsigned NOT NULL DEFAULT 0;
-- =============================================================================
-- PHASE 3: Add missing primary keys
-- =============================================================================
-- Tables without primary keys hurt performance and replication.
-- Uses a helper procedure to safely add `id` column only if it doesn't exist.
DELIMITER //
DROP PROCEDURE IF EXISTS `_add_id_pk_if_missing`//
CREATE PROCEDURE `_add_id_pk_if_missing`(IN tbl VARCHAR(64))
@@ -163,59 +35,7 @@ DELIMITER ;
CALL `_add_id_pk_if_missing`('bot_serves');
CALL `_add_id_pk_if_missing`('chatlogs_room');
CALL `_add_id_pk_if_missing`('commandlogs');
CALL `_add_id_pk_if_missing`('crafting_recipes_ingredients');
-- items_hoppers: use existing item_id as PK (skip if PK already exists)
SET @has_pk = (SELECT COUNT(*) FROM `information_schema`.`TABLE_CONSTRAINTS`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'items_hoppers' AND `CONSTRAINT_TYPE` = 'PRIMARY KEY');
SET @sql = IF(@has_pk = 0, 'ALTER TABLE `items_hoppers` ADD PRIMARY KEY (`item_id`)', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
CALL `_add_id_pk_if_missing`('items_presents');
CALL `_add_id_pk_if_missing`('items_teleports');
CALL `_add_id_pk_if_missing`('namechange_log');
CALL `_add_id_pk_if_missing`('navigator_publics');
CALL `_add_id_pk_if_missing`('pet_breeding');
CALL `_add_id_pk_if_missing`('pet_breeding_races');
CALL `_add_id_pk_if_missing`('pet_drinks');
CALL `_add_id_pk_if_missing`('pet_foods');
CALL `_add_id_pk_if_missing`('pet_items');
CALL `_add_id_pk_if_missing`('pet_vocals');
CALL `_add_id_pk_if_missing`('recycler_prizes');
CALL `_add_id_pk_if_missing`('room_bans');
CALL `_add_id_pk_if_missing`('room_enter_log');
CALL `_add_id_pk_if_missing`('room_game_scores');
CALL `_add_id_pk_if_missing`('room_mutes');
CALL `_add_id_pk_if_missing`('room_rights');
CALL `_add_id_pk_if_missing`('room_trax');
CALL `_add_id_pk_if_missing`('room_trax_playlist');
CALL `_add_id_pk_if_missing`('room_votes');
CALL `_add_id_pk_if_missing`('trax_playlist');
CALL `_add_id_pk_if_missing`('wired_rewards_given');
CALL `_add_id_pk_if_missing`('calendar_rewards_claimed');
-- camera: ensure id is auto-increment PK (skip ADD PK if already exists)
SET @has_pk = (SELECT COUNT(*) FROM `information_schema`.`TABLE_CONSTRAINTS`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'camera' AND `CONSTRAINT_TYPE` = 'PRIMARY KEY');
SET @sql = IF(@has_pk = 0,
'ALTER TABLE `camera` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY (`id`)',
'ALTER TABLE `camera` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Clean up helper procedure
DROP PROCEDURE IF EXISTS `_add_id_pk_if_missing`;
-- =============================================================================
-- PHASE 4: Add missing indexes
-- =============================================================================
-- Adding indexes for columns commonly used in JOINs and WHERE clauses.
-- Uses a helper procedure to skip indexes that already exist.
DELIMITER //
DROP PROCEDURE IF EXISTS `_add_index_if_missing`//
@@ -223,218 +43,22 @@ CREATE PROCEDURE `_add_index_if_missing`(IN tbl VARCHAR(64), IN idx VARCHAR(64),
BEGIN
DECLARE tbl_exists INT DEFAULT 0;
DECLARE idx_exists INT DEFAULT 0;
SELECT COUNT(*) INTO tbl_exists
FROM `information_schema`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl;
IF tbl_exists = 0 THEN
-- Table does not exist, skip
SET @dummy = 0;
ELSE
SELECT COUNT(*) INTO idx_exists
FROM `information_schema`.`STATISTICS`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl AND `INDEX_NAME` = idx;
SELECT COUNT(*) INTO tbl_exists FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl;
IF tbl_exists > 0 THEN
SELECT COUNT(*) INTO idx_exists FROM `information_schema`.`STATISTICS` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl AND `INDEX_NAME` = idx;
IF idx_exists = 0 THEN
SET @sql = CONCAT('ALTER TABLE `', tbl, '` ADD INDEX `', idx, '` (', cols, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END IF;
END//
DELIMITER ;
-- bans
CALL `_add_index_if_missing`('bans', 'idx_bans_user_id', '`user_id`');
CALL `_add_index_if_missing`('bans', 'idx_bans_ip', '`ip`');
CALL `_add_index_if_missing`('bans', 'idx_bans_machine_id', '`machine_id`(64)');
-- calendar_rewards
CALL `_add_index_if_missing`('calendar_rewards', 'idx_calendar_rewards_campaign_id', '`campaign_id`');
-- calendar_rewards_claimed
CALL `_add_index_if_missing`('calendar_rewards_claimed', 'idx_cal_claimed_user_id', '`user_id`');
CALL `_add_index_if_missing`('calendar_rewards_claimed', 'idx_cal_claimed_campaign_id', '`campaign_id`');
CALL `_add_index_if_missing`('calendar_rewards_claimed', 'idx_cal_claimed_reward_id', '`reward_id`');
-- camera
CALL `_add_index_if_missing`('camera', 'idx_camera_user_id', '`user_id`');
CALL `_add_index_if_missing`('camera', 'idx_camera_room_id', '`room_id`');
-- guilds
CALL `_add_index_if_missing`('guilds', 'idx_guilds_user_id', '`user_id`');
CALL `_add_index_if_missing`('guilds', 'idx_guilds_room_id', '`room_id`');
-- guilds_forums_threads
CALL `_add_index_if_missing`('guilds_forums_threads', 'idx_gft_guild_id', '`guild_id`');
CALL `_add_index_if_missing`('guilds_forums_threads', 'idx_gft_opener_id', '`opener_id`');
-- guilds_forums_comments
CALL `_add_index_if_missing`('guilds_forums_comments', 'idx_gfc_user_id', '`user_id`');
-- guild_forum_views
CALL `_add_index_if_missing`('guild_forum_views', 'idx_gfv_user_id', '`user_id`');
CALL `_add_index_if_missing`('guild_forum_views', 'idx_gfv_guild_id', '`guild_id`');
-- items_crackable
CALL `_add_index_if_missing`('items_crackable', 'idx_items_crackable_item_id', '`item_id`');
-- namechange_log
CALL `_add_index_if_missing`('namechange_log', 'idx_namechange_user_id', '`user_id`');
-- messenger_friendrequests
CALL `_add_index_if_missing`('messenger_friendrequests', 'idx_fr_user_to_id', '`user_to_id`');
CALL `_add_index_if_missing`('messenger_friendrequests', 'idx_fr_user_from_id', '`user_from_id`');
-- room_bans
CALL `_add_index_if_missing`('room_bans', 'idx_room_bans_room_id', '`room_id`');
CALL `_add_index_if_missing`('room_bans', 'idx_room_bans_user_id', '`user_id`');
-- room_mutes
CALL `_add_index_if_missing`('room_mutes', 'idx_room_mutes_room_id', '`room_id`');
CALL `_add_index_if_missing`('room_mutes', 'idx_room_mutes_user_id', '`user_id`');
-- room_votes
CALL `_add_index_if_missing`('room_votes', 'idx_room_votes_room_id', '`room_id`');
-- sanctions
CALL `_add_index_if_missing`('sanctions', 'idx_sanctions_habbo_id', '`habbo_id`');
-- shadowbans
CALL `_add_index_if_missing`('shadowbans', 'idx_shadowbans_user_id', '`user_id`');
-- support_cfh_topics
CALL `_add_index_if_missing`('support_cfh_topics', 'idx_cfh_topics_category_id', '`category_id`');
-- support_tickets
CALL `_add_index_if_missing`('support_tickets', 'idx_tickets_sender_id', '`sender_id`');
CALL `_add_index_if_missing`('support_tickets', 'idx_tickets_reported_id', '`reported_id`');
CALL `_add_index_if_missing`('support_tickets', 'idx_tickets_mod_id', '`mod_id`');
CALL `_add_index_if_missing`('support_tickets', 'idx_tickets_room_id', '`room_id`');
-- voucher_history
CALL `_add_index_if_missing`('voucher_history', 'idx_vh_voucher_id', '`voucher_id`');
CALL `_add_index_if_missing`('voucher_history', 'idx_vh_user_id', '`user_id`');
-- ls_name_backgrounds_owned
CALL `_add_index_if_missing`('ls_name_backgrounds_owned', 'idx_lsnbo_user_id', '`user_id`');
CALL `_add_index_if_missing`('ls_name_backgrounds_owned', 'idx_lsnbo_bg_id', '`name_background_id`');
-- ls_name_colors_owned
CALL `_add_index_if_missing`('ls_name_colors_owned', 'idx_lsnco_user_id', '`user_id`');
CALL `_add_index_if_missing`('ls_name_colors_owned', 'idx_lsnco_color_id', '`name_color_id`');
-- ls_prefixes_owned
CALL `_add_index_if_missing`('ls_prefixes_owned', 'idx_lspo_user_id', '`user_id`');
CALL `_add_index_if_missing`('ls_prefixes_owned', 'idx_lspo_prefix_id', '`prefix_id`');
-- users_target_offer_purchases
CALL `_add_index_if_missing`('users_target_offer_purchases', 'idx_utop_offer_id', '`offer_id`');
-- users_unlockable_commands
CALL `_add_index_if_missing`('users_unlockable_commands', 'idx_uuc_user_id', '`user_id`');
-- command_category_permissions
CALL `_add_index_if_missing`('command_category_permissions', 'idx_ccp_category_id', '`category_id`');
-- Clean up helper procedure
DROP PROCEDURE IF EXISTS `_add_index_if_missing`;
-- =============================================================================
-- PHASE 5: Foreign key constraints
-- =============================================================================
-- Adding FK constraints for referential integrity.
-- Using appropriate ON DELETE actions:
-- CASCADE = child rows deleted when parent is deleted
-- SET NULL = child FK set to NULL when parent is deleted (column must be nullable)
-- RESTRICT = prevent parent deletion if children exist (default)
--
-- NOTE: Log/archive tables (chatlogs, commandlogs, room_enter_log, etc.)
-- intentionally do NOT get FKs to avoid cascade-deleting historical data
-- and to keep high-volume inserts fast.
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 5.0 Clean orphan rows BEFORE adding foreign keys
-- ---------------------------------------------------------------------------
-- Legacy Habbo databases often have rows referencing deleted users, rooms, or
-- items. These orphans must be removed before FK constraints can be created.
-- Uses a helper procedure to safely skip tables that don't exist.
DELIMITER //
DROP PROCEDURE IF EXISTS `_delete_orphans`//
CREATE PROCEDURE `_delete_orphans`(IN tbl VARCHAR(64), IN col VARCHAR(64), IN ref_tbl VARCHAR(64), IN ref_col VARCHAR(64), IN extra_where VARCHAR(255))
BEGIN
DECLARE tbl_exists INT DEFAULT 0;
DECLARE ref_exists INT DEFAULT 0;
SELECT COUNT(*) INTO tbl_exists
FROM `information_schema`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl;
SELECT COUNT(*) INTO ref_exists
FROM `information_schema`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = ref_tbl;
IF tbl_exists > 0 AND ref_exists > 0 THEN
IF extra_where IS NOT NULL AND extra_where != '' THEN
SET @sql = CONCAT('DELETE FROM `', tbl, '` WHERE ', extra_where, ' AND `', col, '` NOT IN (SELECT `', ref_col, '` FROM `', ref_tbl, '`)');
ELSE
SET @sql = CONCAT('DELETE FROM `', tbl, '` WHERE `', col, '` NOT IN (SELECT `', ref_col, '` FROM `', ref_tbl, '`)');
END IF;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END//
DELIMITER ;
-- Orphans referencing users
CALL `_delete_orphans`('rooms', 'owner_id', 'users', 'id', '');
CALL `_delete_orphans`('items', 'user_id', 'users', 'id', '');
-- bots: skipped, no FK added (see 5.3 comment)
CALL `_delete_orphans`('bans', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('bans', 'user_staff_id', 'users', 'id', '');
CALL `_delete_orphans`('guilds', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('guilds_members', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('guilds_forums_comments', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('guild_forum_views', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_settings', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_badges', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_currency', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_effects', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_clothing', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_favorite_rooms', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_wardrobe', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_pets', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_recipes', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_saved_searches', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_navigator_settings', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_achievements', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_achievements_queue', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_ignored', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_ignored', 'target_id', 'users', 'id', '');
CALL `_delete_orphans`('users_subscriptions', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_target_offer_purchases', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_unlockable_outfit', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_window_settings', 'user_id', 'users', 'id', '');
CALL `_delete_orphans`('users_prefixes', 'user_id', 'users', 'id', '');
-- Orphans referencing rooms
CALL `_delete_orphans`('guilds', 'room_id', 'rooms', 'id', '');
CALL `_delete_orphans`('items', 'room_id', 'rooms', 'id', '`room_id` != 0');
-- Orphans referencing items_base
CALL `_delete_orphans`('items', 'item_id', 'items_base', 'id', '');
-- Orphans referencing guilds
CALL `_delete_orphans`('guilds_members', 'guild_id', 'guilds', 'id', '');
CALL `_delete_orphans`('guilds_forums_threads', 'guild_id', 'guilds', 'id', '');
CALL `_delete_orphans`('guild_forum_views', 'guild_id', 'guilds', 'id', '');
-- Clean up helper procedure
DROP PROCEDURE IF EXISTS `_delete_orphans`;
-- ---------------------------------------------------------------------------
-- Helper: add FK only if it doesn't already exist
-- ---------------------------------------------------------------------------
DELIMITER //
DROP PROCEDURE IF EXISTS `_add_fk_if_missing`//
CREATE PROCEDURE `_add_fk_if_missing`(IN tbl VARCHAR(64), IN fk_name VARCHAR(64), IN col VARCHAR(64), IN ref_tbl VARCHAR(64), IN ref_col VARCHAR(64), IN on_del VARCHAR(20))
@@ -442,226 +66,27 @@ BEGIN
DECLARE tbl_exists INT DEFAULT 0;
DECLARE ref_exists INT DEFAULT 0;
DECLARE fk_exists INT DEFAULT 0;
SELECT COUNT(*) INTO tbl_exists
FROM `information_schema`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl;
SELECT COUNT(*) INTO ref_exists
FROM `information_schema`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = ref_tbl;
IF tbl_exists = 0 OR ref_exists = 0 THEN
-- Source or referenced table does not exist, skip
SET @dummy = 0;
ELSE
SELECT COUNT(*) INTO fk_exists
FROM `information_schema`.`TABLE_CONSTRAINTS`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl AND `CONSTRAINT_NAME` = fk_name AND `CONSTRAINT_TYPE` = 'FOREIGN KEY';
SELECT COUNT(*) INTO tbl_exists FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl;
SELECT COUNT(*) INTO ref_exists FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = ref_tbl;
IF tbl_exists > 0 AND ref_exists > 0 THEN
SELECT COUNT(*) INTO fk_exists FROM `information_schema`.`TABLE_CONSTRAINTS` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl AND `CONSTRAINT_NAME` = fk_name;
IF fk_exists = 0 THEN
SET @sql = CONCAT('ALTER TABLE `', tbl, '` ADD CONSTRAINT `', fk_name, '` FOREIGN KEY (`', col, '`) REFERENCES `', ref_tbl, '` (`', ref_col, '`) ON DELETE ', on_del);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END IF;
END//
DELIMITER ;
-- 5.1 Rooms → Users
CALL `_add_fk_if_missing`('rooms', 'fk_rooms_owner', 'owner_id', 'users', 'id', 'CASCADE');
-- 5.2 Items → Users, Items_base
CALL `_add_fk_if_missing`('items', 'fk_items_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('items', 'fk_items_item_base', 'item_id', 'items_base', 'id', 'CASCADE');
-- 5.3 Bots → Users
-- SKIPPED: The emulator creates bots with a temporary user_id during catalog
-- purchase, which may not yet exist in `users`. Drop FK if previously added.
SET @has_fk = (SELECT COUNT(*) FROM `information_schema`.`TABLE_CONSTRAINTS`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'bots' AND `CONSTRAINT_NAME` = 'fk_bots_user' AND `CONSTRAINT_TYPE` = 'FOREIGN KEY');
SET @sql = IF(@has_fk > 0, 'ALTER TABLE `bots` DROP FOREIGN KEY `fk_bots_user`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 5.4 Bans → Users
CALL `_add_fk_if_missing`('bans', 'fk_bans_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('bans', 'fk_bans_staff', 'user_staff_id', 'users', 'id', 'CASCADE');
-- 5.5 Guilds → Users, Rooms
CALL `_add_fk_if_missing`('guilds', 'fk_guilds_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('guilds', 'fk_guilds_room', 'room_id', 'rooms', 'id', 'CASCADE');
-- 5.6 Guild members → Guilds, Users
CALL `_add_fk_if_missing`('guilds_members', 'fk_gm_guild', 'guild_id', 'guilds', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('guilds_members', 'fk_gm_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.7 Guild forum threads → Guilds, Users
CALL `_add_fk_if_missing`('guilds_forums_threads', 'fk_gft_guild', 'guild_id', 'guilds', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('guilds_forums_threads', 'fk_gft_opener', 'opener_id', 'users', 'id', 'SET NULL');
-- 5.8 Guild forum comments → Threads, Users
CALL `_add_fk_if_missing`('guilds_forums_comments', 'fk_gfc_thread', 'thread_id', 'guilds_forums_threads', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('guilds_forums_comments', 'fk_gfc_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.9 Guild forum views → Guilds, Users
CALL `_add_fk_if_missing`('guild_forum_views', 'fk_gfv_guild', 'guild_id', 'guilds', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('guild_forum_views', 'fk_gfv_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.10 Catalog items → Catalog pages
CALL `_add_fk_if_missing`('catalog_items', 'fk_catitems_page', 'page_id', 'catalog_pages', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('guilds', 'fk_guilds_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.11 Catalog items limited → Catalog items
CALL `_add_fk_if_missing`('catalog_items_limited', 'fk_catitemsltd_catitem', 'catalog_item_id', 'catalog_items', 'id', 'CASCADE');
-- 5.12 Calendar rewards → Calendar campaigns
CALL `_add_fk_if_missing`('calendar_rewards', 'fk_calrewards_campaign', 'campaign_id', 'calendar_campaigns', 'id', 'CASCADE');
-- 5.13 Calendar rewards claimed → Users, Campaigns, Rewards
CALL `_add_fk_if_missing`('calendar_rewards_claimed', 'fk_calclaimed_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('calendar_rewards_claimed', 'fk_calclaimed_campaign', 'campaign_id', 'calendar_campaigns', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('calendar_rewards_claimed', 'fk_calclaimed_reward', 'reward_id', 'calendar_rewards', 'id', 'CASCADE');
-- 5.14-5.26 Users_* → Users
CALL `_add_fk_if_missing`('users_settings', 'fk_usettings_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_badges', 'fk_ubadges_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_currency', 'fk_ucurrency_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_effects', 'fk_ueffects_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_clothing', 'fk_uclothing_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_favorite_rooms', 'fk_ufavrooms_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_favorite_rooms', 'fk_ufavrooms_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_wardrobe', 'fk_uwardrobe_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_pets', 'fk_upets_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_recipes', 'fk_urecipes_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_saved_searches', 'fk_usavedsearches_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_navigator_settings', 'fk_unavsettings_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_achievements', 'fk_uachievements_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_achievements_queue', 'fk_uachqueue_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_ignored', 'fk_uignored_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_ignored', 'fk_uignored_target', 'target_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_subscriptions', 'fk_usubs_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_target_offer_purchases', 'fk_utop_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_target_offer_purchases', 'fk_utop_offer', 'offer_id', 'catalog_target_offers', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('users_unlockable_commands', 'fk_uunlockable_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('user_window_settings', 'fk_uwinsettings_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('user_prefixes', 'fk_uprefixes_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.33-5.35 Messenger → Users
CALL `_add_fk_if_missing`('messenger_friendships', 'fk_mfriends_user_one', 'user_one_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('messenger_friendships', 'fk_mfriends_user_two', 'user_two_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('messenger_friendrequests', 'fk_mfr_user_to', 'user_to_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('messenger_friendrequests', 'fk_mfr_user_from', 'user_from_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('messenger_categories', 'fk_mcat_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.36 Marketplace → Items, Users
CALL `_add_fk_if_missing`('marketplace_items', 'fk_market_item', 'item_id', 'items', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('marketplace_items', 'fk_market_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.37-5.44 Room_* → Rooms, Users
CALL `_add_fk_if_missing`('room_rights', 'fk_rrights_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_rights', 'fk_rrights_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_bans', 'fk_rbans_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_bans', 'fk_rbans_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_mutes', 'fk_rmutes_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_mutes', 'fk_rmutes_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_votes', 'fk_rvotes_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_votes', 'fk_rvotes_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_wordfilter', 'fk_rwf_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_promotions', 'fk_rpromo_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_trax', 'fk_rtrax_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_trax_playlist', 'fk_rtraxpl_room', 'room_id', 'rooms', 'id', 'CASCADE');
-- 5.45 Rooms_for_sale → Rooms, Users
CALL `_add_fk_if_missing`('rooms_for_sale', 'fk_r4sale_room', 'room_id', 'rooms', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('rooms_for_sale', 'fk_r4sale_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.46-5.47 Room_trade_log → Users
CALL `_add_fk_if_missing`('room_trade_log', 'fk_rtlog_user_one', 'user_one_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_trade_log', 'fk_rtlog_user_two', 'user_two_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_trade_log_items', 'fk_rtli_trade', 'id', 'room_trade_log', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('room_trade_log_items', 'fk_rtli_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.48-5.49 Polls → Polls, Users
CALL `_add_fk_if_missing`('polls_questions', 'fk_pq_poll', 'poll_id', 'polls', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('polls_answers', 'fk_pa_poll', 'poll_id', 'polls', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('polls_answers', 'fk_pa_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.50-5.51 Crafting → Crafting_recipes
CALL `_add_fk_if_missing`('crafting_recipes_ingredients', 'fk_cri_recipe', 'recipe_id', 'crafting_recipes', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('crafting_altars_recipes', 'fk_car_recipe', 'recipe_id', 'crafting_recipes', 'id', 'CASCADE');
-- 5.52 Voucher_history → Vouchers, Users
CALL `_add_fk_if_missing`('voucher_history', 'fk_vh_voucher', 'voucher_id', 'vouchers', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('voucher_history', 'fk_vh_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.53-5.55 Support, Commands, Economy
CALL `_add_fk_if_missing`('support_cfh_topics', 'fk_cfhtopics_category', 'category_id', 'support_cfh_categories', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('command_category_permissions', 'fk_ccp_category', 'category_id', 'command_categories', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('economy_furniture', 'fk_econfurni_itembase', 'items_base_id', 'items_base', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('economy_furniture', 'fk_econfurni_category', 'economy_categories_id', 'economy_categories', 'id', 'CASCADE');
-- 5.56-5.57 Sanctions, Camera → Users
CALL `_add_fk_if_missing`('sanctions', 'fk_sanctions_user', 'habbo_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('camera_web', 'fk_cameraweb_user', 'user_id', 'users', 'id', 'CASCADE');
-- 5.58 LS ownership → Users, LS definitions
CALL `_add_fk_if_missing`('ls_name_backgrounds_owned', 'fk_lsnbo_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('ls_name_backgrounds_owned', 'fk_lsnbo_bg', 'name_background_id', 'ls_name_backgrounds', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('ls_name_colors_owned', 'fk_lsnco_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('ls_name_colors_owned', 'fk_lsnco_color', 'name_color_id', 'ls_name_colors', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('ls_prefixes_owned', 'fk_lspo_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('ls_prefixes_owned', 'fk_lspo_prefix', 'prefix_id', 'ls_prefixes', 'id', 'CASCADE');
-- 5.59 Navigator_publics → Navigator_publiccats, Rooms
CALL `_add_fk_if_missing`('navigator_publics', 'fk_navpub_cat', 'public_cat_id', 'navigator_publiccats', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('navigator_publics', 'fk_navpub_room', 'room_id', 'rooms', 'id', 'CASCADE');
-- 5.60 GOTW winners → Users
CALL `_add_fk_if_missing`('gotw_winners', 'fk_gotw_user', 'user_id', 'users', 'id', 'CASCADE');
-- Clean up helper procedure
DROP PROCEDURE IF EXISTS `_add_id_pk_if_missing`;
DROP PROCEDURE IF EXISTS `_add_index_if_missing`;
DROP PROCEDURE IF EXISTS `_add_fk_if_missing`;
-- =============================================================================
-- PHASE 6: Charset standardization
-- =============================================================================
-- Standardize remaining utf8mb3 tables to utf8mb4 for full Unicode support.
ALTER TABLE IF EXISTS `guilds`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `guilds_elements`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `groups_items`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `messenger_friendships`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `room_rights`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `soundtracks`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `users_achievements_queue`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `users_saved_searches`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `users_target_offer_purchases`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `wordfilter`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE IF EXISTS `logs_shop_purchases`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- =============================================================================
-- Done - Re-enable foreign key checks
-- =============================================================================
SET FOREIGN_KEY_CHECKS = 1;
SET SQL_MODE = @OLD_SQL_MODE;
SET SQL_MODE = @OLD_SQL_MODE;