local picchat_table = "lbpicchat_logged_in" local picchat_phone_column = "phone_number" local sky_phone_table = "sky_phone_sims" local sky_phone_column = "phone_number" local sky_phone_constraint = "fk_lbpicchat_logged_in_sky_phone" local function quote_identifier(identifier) if type(identifier) ~= "string" or not identifier:match("^[%w_]+$") then error(("[sky_phone] Rejected invalid database identifier '%s'."):format(tostring(identifier))) end return ("`%s`"):format(identifier) end local function migrate_picchat_phone_reference() local columns = Bridge.Database.Query([[ SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ? LIMIT 1 ]], { picchat_table, picchat_phone_column }) if not columns[1] then return end local references = Bridge.Database.Query([[ SELECT `CONSTRAINT_NAME`, `REFERENCED_TABLE_NAME`, `REFERENCED_COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`KEY_COLUMN_USAGE` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ? AND `REFERENCED_TABLE_NAME` IS NOT NULL ]], { picchat_table, picchat_phone_column }) local has_sky_phone_reference = false for _, reference in ipairs(references) do local referenced_table = tostring(reference.REFERENCED_TABLE_NAME or reference.referenced_table_name):lower() local referenced_column = tostring(reference.REFERENCED_COLUMN_NAME or reference.referenced_column_name):lower() local constraint_name = reference.CONSTRAINT_NAME or reference.constraint_name if referenced_table == sky_phone_table and referenced_column == sky_phone_column then has_sky_phone_reference = true elseif referenced_table == "phone_phones" and referenced_column == picchat_phone_column then Bridge.Database.Query(("ALTER TABLE %s DROP FOREIGN KEY %s"):format( quote_identifier(picchat_table), quote_identifier(constraint_name) ), {}) Bridge.Debug( "info", "[sky_phone] Removed legacy PicChat phone reference '%s'.", tostring(constraint_name), { notice = true } ) else Bridge.Debug( "error", "[sky_phone] PicChat phone identity uses unsupported reference %s.%s; automatic migration stopped.", referenced_table, referenced_column ) return end end if has_sky_phone_reference then return end Bridge.Database.Query(([[ ALTER TABLE %s MODIFY COLUMN %s VARCHAR(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL ]]):format( quote_identifier(picchat_table), quote_identifier(picchat_phone_column) ), {}) local orphan_rows = Bridge.Database.Query(([[ SELECT COUNT(*) AS `count` FROM %s picchat LEFT JOIN %s sim ON sim.%s = picchat.%s WHERE picchat.%s IS NOT NULL AND sim.%s IS NULL ]]):format( quote_identifier(picchat_table), quote_identifier(sky_phone_table), quote_identifier(sky_phone_column), quote_identifier(picchat_phone_column), quote_identifier(picchat_phone_column), quote_identifier(sky_phone_column) ), {}) local orphan_count = orphan_rows[1] and tonumber(orphan_rows[1].count) or 0 if orphan_count > 0 then Bridge.Debug( "warn", "[sky_phone] PicChat contains %s account(s) without a Sky SIM. Legacy data was preserved; run 'skyphone:migrate lb-phone' and restart sky_phone to restore the foreign key.", tostring(orphan_count) ) return end Bridge.Database.Query(([[ ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) ON DELETE CASCADE ON UPDATE CASCADE ]]):format( quote_identifier(picchat_table), quote_identifier(sky_phone_constraint), quote_identifier(picchat_phone_column), quote_identifier(sky_phone_table), quote_identifier(sky_phone_column) ), {}) Bridge.Debug( "info", "[sky_phone] PicChat phone identities now reference sky_phone_sims.", { notice = true } ) end local function run_picchat_phone_reference_migration() local success, migration_error = xpcall(migrate_picchat_phone_reference, debug.traceback) if success then return end Bridge.Debug( "error", "[sky_phone] PicChat compatibility migration failed. Sky Phone startup will continue, but PicChat database compatibility may be unavailable: %s.", tostring(migration_error) ) end Bridge.Database.AfterMigration("sky_phone", run_picchat_phone_reference_migration)