From adb7e4dba8b7f57a135aa211e0bd96756a9867f9 Mon Sep 17 00:00:00 2001 From: Shauren Date: Sat, 1 Aug 2020 15:15:07 +0200 Subject: [PATCH] Refactor mail loading to execute 2 database queries instead of 1+mails.size() --- .../Implementation/CharacterDatabase.cpp | 4 +- src/server/game/Entities/Player/Player.cpp | 181 +++++++++--------- src/server/game/Entities/Player/Player.h | 2 +- 3 files changed, 92 insertions(+), 95 deletions(-) diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 7149b880901..3227439fc64 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -128,7 +128,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES, "SELECT instanceId, releaseTime FROM account_instance_times WHERE accountId = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_MAILITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, creationTime, text, item_guid, itemEntry, owner_guid FROM mail_items mi JOIN item_instance ii ON mi.item_guid = ii.guid WHERE mail_id = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_MAILITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, creationTime, text, item_guid, itemEntry, ii.owner_guid, m.id FROM mail_items mi INNER JOIN mail m ON mi.mail_id = m.id LEFT JOIN item_instance ii ON mi.item_guid = ii.guid WHERE m.receiver = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_AUCTION_ITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, creationTime, text, itemguid, itemEntry FROM auctionhouse ah JOIN item_instance ii ON ah.itemguid = ii.guid", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_AUCTIONS, "SELECT id, houseid, itemguid, itemEntry, count, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid", CONNECTION_SYNCH); PrepareStatement(CHAR_INS_AUCTION, "INSERT INTO auctionhouse (id, houseid, itemguid, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); @@ -467,7 +467,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL, "SELECT id, messageType, mailTemplateId, sender, subject, body, money, has_items FROM mail WHERE receiver = ? AND has_items <> 0 AND cod <> 0", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_SOCIAL, "SELECT DISTINCT guid FROM character_social WHERE friend = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId FROM mail WHERE receiver = ? ORDER BY id DESC", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId FROM mail WHERE receiver = ? ORDER BY id DESC", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_CHAR_AURA_FROZEN, "DELETE FROM character_aura WHERE spell = 9454 AND guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHAR_INVENTORY_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE itemEntry = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_MAIL_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM mail_items mi INNER JOIN item_instance ii ON ii.guid = mi.item_guid WHERE itemEntry = ?", CONNECTION_SYNCH); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index d27a45ed721..693f8bfb28c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -3833,6 +3833,24 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe if (resultMail) { + std::unordered_map> itemsByMail; + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); + stmt->setUInt32(0, guid); + PreparedQueryResult resultItems = CharacterDatabase.Query(stmt); + + if (resultItems) + { + do + { + Field* fields = resultItems->Fetch(); + uint32 mailId = fields[14].GetUInt32(); + if (Item* mailItem = _LoadMailedItem(playerguid, nullptr, mailId, nullptr, fields)) + itemsByMail[mailId].push_back(mailItem); + + } while (resultItems->NextRow()); + } + do { Field* mailFields = resultMail->Fetch(); @@ -3868,41 +3886,14 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe if (mailTemplateId) draft = MailDraft(mailTemplateId, false); // items are already included - if (has_items) + auto itemsItr = itemsByMail.find(mail_id); + if (itemsItr != itemsByMail.end()) { - // Data needs to be at first place for Item::LoadFromDB - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); - stmt->setUInt32(0, mail_id); - PreparedQueryResult resultItems = CharacterDatabase.Query(stmt); - if (resultItems) - { - do - { - Field* itemFields = resultItems->Fetch(); - ObjectGuid::LowType item_guidlow = itemFields[11].GetUInt32(); - uint32 item_template = itemFields[12].GetUInt32(); + for (Item* item : itemsItr->second) + draft.AddItem(item); - ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(item_template); - if (!itemProto) - { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); - stmt->setUInt32(0, item_guidlow); - trans->Append(stmt); - continue; - } - - Item* pItem = NewItemOrBag(itemProto); - if (!pItem->LoadFromDB(item_guidlow, playerguid, itemFields, item_template)) - { - pItem->FSetState(ITEM_REMOVED); - pItem->SaveToDB(trans); // it also deletes item object! - continue; - } - - draft.AddItem(pItem); - } - while (resultItems->NextRow()); - } + // MailDraft will take care of freeing memory + itemsByMail.erase(itemsItr); } stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID); @@ -18324,61 +18315,54 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction& trans, uint32 zoneId, uint } // load mailed item which should receive current player -void Player::_LoadMailedItems(Mail* mail) +Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint32 mailId, Mail* mail, Field* fields) { - // data needs to be at first place for Item::LoadFromDB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); - stmt->setUInt32(0, mail->messageID); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (!result) - return; + ObjectGuid::LowType itemGuid = fields[11].GetUInt32(); + uint32 itemEntry = fields[12].GetUInt32(); - do + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry); + if (!proto) { - Field* fields = result->Fetch(); + TC_LOG_ERROR("entities.player", "Player '%s' (%s) has unknown item in mailed items (GUID: %u, Entry: %u) in mail (%u), deleted.", + player ? player->GetName().c_str() : "", playerGuid.ToString().c_str(), itemGuid, itemEntry, mailId); - ObjectGuid::LowType itemGuid = fields[11].GetUInt32(); - uint32 itemTemplate = fields[12].GetUInt32(); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - mail->AddItem(itemGuid, itemTemplate); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); + stmt->setUInt32(0, itemGuid); + trans->Append(stmt); - ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemTemplate); + Item::DeleteFromDB(trans, itemGuid); - if (!proto) - { - TC_LOG_ERROR("entities.player", "Player '%s' (%s) has unknown item_template in mailed items (GUID: %u, Entry: %u) in mail (%u), deleted.", - GetName().c_str(), GetGUID().ToString().c_str(), itemGuid, itemTemplate, mail->messageID); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); - stmt->setUInt32(0, itemGuid); - CharacterDatabase.Execute(stmt); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); - stmt->setUInt32(0, itemGuid); - CharacterDatabase.Execute(stmt); - continue; - } - - Item* item = NewItemOrBag(proto); - - if (!item->LoadFromDB(itemGuid, ObjectGuid(HighGuid::Player, fields[13].GetUInt32()), fields, itemTemplate)) - { - TC_LOG_ERROR("entities.player", "Player::_LoadMailedItems: Item (GUID: %u) in mail (%u) doesn't exist, deleted from mail.", itemGuid, mail->messageID); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM); - stmt->setUInt32(0, itemGuid); - CharacterDatabase.Execute(stmt); - - item->FSetState(ITEM_REMOVED); - - CharacterDatabaseTransaction temp = CharacterDatabaseTransaction(nullptr); - item->SaveToDB(temp); // it also deletes item object ! - continue; - } - - AddMItem(item); + CharacterDatabase.CommitTransaction(trans); + return nullptr; } - while (result->NextRow()); + + Item* item = NewItemOrBag(proto); + + ObjectGuid ownerGuid = fields[13].GetUInt32() ? ObjectGuid::Create(fields[13].GetUInt32()) : ObjectGuid::Empty; + if (!item->LoadFromDB(itemGuid, ownerGuid, fields, itemEntry)) + { + TC_LOG_ERROR("entities.player", "Player::_LoadMailedItems: Item (GUID: %u) in mail (%u) doesn't exist, deleted from mail.", itemGuid, mailId); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM); + stmt->setUInt32(0, itemGuid); + CharacterDatabase.Execute(stmt); + + item->FSetState(ITEM_REMOVED); + + CharacterDatabaseTransaction temp = CharacterDatabaseTransaction(nullptr); + item->SaveToDB(temp); // it also deletes item object ! + return nullptr; + } + + if (mail) + mail->AddItem(itemGuid, itemEntry); + + if (player) + player->AddMItem(item); + + return item; } void Player::_LoadMailInit(PreparedQueryResult resultUnread, PreparedQueryResult resultDelivery) @@ -18402,6 +18386,8 @@ void Player::_LoadMail() stmt->setUInt32(0, GetGUID().GetCounter()); PreparedQueryResult result = CharacterDatabase.Query(stmt); + std::unordered_map mailById; + if (result) { do @@ -18415,14 +18401,13 @@ void Player::_LoadMail() m->receiver = fields[3].GetUInt32(); m->subject = fields[4].GetString(); m->body = fields[5].GetString(); - bool has_items = fields[6].GetBool(); - m->expire_time = time_t(fields[7].GetUInt32()); - m->deliver_time = time_t(fields[8].GetUInt32()); - m->money = fields[9].GetUInt64(); - m->COD = fields[10].GetUInt64(); - m->checked = fields[11].GetUInt8(); - m->stationery = fields[12].GetUInt8(); - m->mailTemplateId = fields[13].GetInt16(); + m->expire_time = time_t(fields[6].GetUInt32()); + m->deliver_time = time_t(fields[7].GetUInt32()); + m->money = fields[8].GetUInt32(); + m->COD = fields[9].GetUInt32(); + m->checked = fields[10].GetUInt8(); + m->stationery = fields[11].GetUInt8(); + m->mailTemplateId = fields[12].GetInt16(); if (m->mailTemplateId && !sMailTemplateStore.LookupEntry(m->mailTemplateId)) { @@ -18431,14 +18416,26 @@ void Player::_LoadMail() } m->state = MAIL_STATE_UNCHANGED; - - if (has_items) - _LoadMailedItems(m); - m_mail.push_back(m); + mailById[m->messageID] = m; } while (result->NextRow()); } + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); + stmt->setUInt32(0, GetGUID().GetCounter()); + result = CharacterDatabase.Query(stmt); + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 mailId = fields[14].GetUInt32(); + _LoadMailedItem(GetGUID(), this, mailId, mailById[mailId], fields); + } while (result->NextRow()); + } + m_mailsLoaded = true; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index aa36a7a1d63..1e5e79f2673 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2467,7 +2467,7 @@ class TC_GAME_API Player : public Unit, public GridObject void _LoadVoidStorage(PreparedQueryResult result); void _LoadMailInit(PreparedQueryResult resultUnread, PreparedQueryResult resultDelivery); void _LoadMail(); - void _LoadMailedItems(Mail* mail); + static Item* _LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint32 mailId, Mail* mail, Field* fields); void _LoadQuestStatus(PreparedQueryResult result); void _LoadQuestStatusRewarded(PreparedQueryResult result); void _LoadDailyQuestStatus(PreparedQueryResult result);