🆙 Stage 1 of DB rebuild

This commit is contained in:
duckietm
2026-04-07 13:53:21 +02:00
parent 75b73df8dc
commit 46230d0709
11 changed files with 244 additions and 152 deletions
@@ -88,5 +88,10 @@ 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`;
# Make sure thenb System account exists
INSERT INTO `users` (`id`, `username`, `password`, `ip_register`, `ip_current`, `motto`, `look`, `rank`, `credits`)
SELECT 0, '[SYSTEM]', '!', '127.0.0.1', '127.0.0.1', 'System sentinel - do not delete', '', 1, 0
WHERE NOT EXISTS (SELECT 1 FROM `users` WHERE `id` = 0);
SET FOREIGN_KEY_CHECKS = 1;
SET SQL_MODE = @OLD_SQL_MODE;
@@ -4,16 +4,15 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.core.ConfigurationManager;
import com.zaxxer.hikari.HikariDataSource;
import gnu.trove.map.hash.THashMap;
import gnu.trove.set.hash.THashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Database {
@@ -48,8 +47,6 @@ public class Database {
}
public void dispose() {
// dataSource is the same instance returned by databasePool.getDatabase(),
// so close it exactly once.
if (this.dataSource != null && !this.dataSource.isClosed()) {
this.dataSource.close();
}
@@ -63,51 +60,77 @@ public class Database {
return this.databasePool;
}
public static PreparedStatement preparedStatementWithParams(Connection connection, String query, THashMap<String, Object> queryParams) throws SQLException {
THashMap<Integer, Object> params = new THashMap<Integer, Object>();
THashSet<String> quotedParams = new THashSet<>();
public static PreparedStatement preparedStatementWithParams(Connection connection,
String query,
Map<String, Object> queryParams) throws SQLException {
StringBuilder positional = new StringBuilder(query.length());
List<Object> bindValues = new ArrayList<>();
for(String key : queryParams.keySet()) {
quotedParams.add(Pattern.quote(key));
}
int i = 0;
int n = query.length();
String regex = "(" + String.join("|", quotedParams) + ")";
while (i < n) {
char c = query.charAt(i);
Matcher m = Pattern.compile(regex).matcher(query);
int i = 1;
while (m.find()) {
try {
params.put(i, queryParams.get(m.group(1)));
if (c == '\'') {
positional.append(c);
i++;
while (i < n) {
char inner = query.charAt(i);
positional.append(inner);
i++;
if (inner == '\'') {
if (i < n && query.charAt(i) == '\'') {
positional.append('\'');
i++;
} else {
break;
}
}
}
continue;
}
catch (Exception ignored) { }
if (c == '@' && i + 1 < n && isNameStart(query.charAt(i + 1))) {
int start = i;
int j = i + 1;
while (j < n && isNamePart(query.charAt(j))) {
j++;
}
String name = query.substring(start, j);
if (!queryParams.containsKey(name)) {
throw new IllegalArgumentException(
"SQL template references parameter '" + name + "' but no value was provided");
}
positional.append('?');
bindValues.add(queryParams.get(name));
i = j;
continue;
}
positional.append(c);
i++;
}
PreparedStatement statement = connection.prepareStatement(query.replaceAll(regex, "?"));
for(Map.Entry<Integer, Object> set : params.entrySet()) {
if(set.getValue().getClass() == String.class) {
statement.setString(set.getKey(), (String)set.getValue());
}
else if(set.getValue().getClass() == Integer.class) {
statement.setInt(set.getKey(), (Integer)set.getValue());
}
else if(set.getValue().getClass() == Double.class) {
statement.setDouble(set.getKey(), (Double)set.getValue());
}
else if(set.getValue().getClass() == Float.class) {
statement.setFloat(set.getKey(), (Float)set.getValue());
}
else if(set.getValue().getClass() == Long.class) {
statement.setLong(set.getKey(), (Long)set.getValue());
}
else {
statement.setObject(set.getKey(), set.getValue());
}
PreparedStatement statement = connection.prepareStatement(positional.toString());
for (int k = 0; k < bindValues.size(); k++) {
statement.setObject(k + 1, bindValues.get(k));
}
return statement;
}
@Deprecated
public static PreparedStatement preparedStatementWithParams(Connection connection,
String query,
THashMap<String, Object> queryParams) throws SQLException {
return preparedStatementWithParams(connection, query, (Map<String, Object>) queryParams);
}
private static boolean isNameStart(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
private static boolean isNamePart(char c) {
return isNameStart(c) || (c >= '0' && c <= '9');
}
}
@@ -0,0 +1,95 @@
package com.eu.habbo.database;
import com.eu.habbo.Emulator;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
public final class SqlQueries {
private SqlQueries() {
}
@FunctionalInterface
public interface RowMapper<T> {
T map(ResultSet rs) throws SQLException;
}
@FunctionalInterface
public interface ParameterBinder<P> {
void bind(PreparedStatement ps, P value) throws SQLException;
}
public static class DataAccessException extends RuntimeException {
public DataAccessException(String message, Throwable cause) {
super(message, cause);
}
}
public static <T> List<T> query(String sql, RowMapper<T> mapper, Object... params) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
bindAll(ps, params);
try (ResultSet rs = ps.executeQuery()) {
List<T> out = new ArrayList<>();
while (rs.next()) {
out.add(mapper.map(rs));
}
return out;
}
} catch (SQLException e) {
throw new DataAccessException("query failed: " + sql, e);
}
}
public static <T> Optional<T> queryOne(String sql, RowMapper<T> mapper, Object... params) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
bindAll(ps, params);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? Optional.ofNullable(mapper.map(rs)) : Optional.empty();
}
} catch (SQLException e) {
throw new DataAccessException("queryOne failed: " + sql, e);
}
}
public static int update(String sql, Object... params) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
bindAll(ps, params);
return ps.executeUpdate();
} catch (SQLException e) {
throw new DataAccessException("update failed: " + sql, e);
}
}
public static <P> int[] batchUpdate(String sql, List<P> items, ParameterBinder<P> binder) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
for (P item : items) {
binder.bind(ps, item);
ps.addBatch();
}
return ps.executeBatch();
} catch (SQLException e) {
throw new DataAccessException("batchUpdate failed: " + sql, e);
}
}
private static void bindAll(PreparedStatement ps, Object[] params) throws SQLException {
if (params == null) {
return;
}
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
}
@@ -84,7 +84,7 @@ public class UserInfoCommand extends Command {
if (onlineHabbo != null) {
message.append("\r" + "<b>Other accounts (");
ArrayList<HabboInfo> users = Emulator.getGameEnvironment().getHabboManager().getCloneAccounts(onlineHabbo, 10);
List<HabboInfo> users = Emulator.getGameEnvironment().getHabboManager().getCloneAccounts(onlineHabbo, 10);
users.sort(new Comparator<HabboInfo>() {
@Override
public int compare(HabboInfo o1, HabboInfo o2) {
@@ -638,7 +638,7 @@ public class ItemManager {
statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) {
try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO items_presents VALUES (?, ?)")) {
try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO items_presents (item_id, base_item_reward) VALUES (?, ?)")) {
while (set.next() && item == null) {
preparedStatement.setInt(1, set.getInt(1));
preparedStatement.setInt(2, Integer.parseInt(itemId));
@@ -2151,7 +2151,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
this.rightsManager.refreshRightsForHabbo(habbo);
}
public THashMap<Integer, String> getUsersWithRights() {
public Map<Integer, String> getUsersWithRights() {
return this.rightsManager.getUsersWithRights();
}
@@ -496,7 +496,7 @@ public class RoomManager {
h.getClient().sendResponse(new RoomScoreComposer(room.getScore(), !this.hasVotedForRoom(h, room)));
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO room_votes VALUES (?, ?)")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO room_votes (user_id, room_id) VALUES (?, ?)")) {
statement.setInt(1, habbo.getHabboInfo().getId());
statement.setInt(2, room.getId());
statement.execute();
@@ -1,6 +1,7 @@
package com.eu.habbo.habbohotel.rooms;
import com.eu.habbo.Emulator;
import com.eu.habbo.database.SqlQueries;
import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.users.Habbo;
@@ -14,7 +15,6 @@ import com.eu.habbo.habbohotel.messenger.MessengerBuddy;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.plugin.events.users.UserRightsTakenEvent;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.THashMap;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import org.slf4j.Logger;
@@ -24,6 +24,9 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Manages room rights, bans, and mutes.
@@ -149,13 +152,11 @@ public class RoomRightsManager {
}
if (this.rights.add(userId)) {
try (Connection connection = Emulator.getDatabase().getDataSource()
.getConnection(); PreparedStatement statement = connection.prepareStatement(
"INSERT INTO room_rights VALUES (?, ?)")) {
statement.setInt(1, this.room.getId());
statement.setInt(2, userId);
statement.execute();
} catch (SQLException e) {
try {
SqlQueries.update(
"INSERT INTO room_rights (room_id, user_id) VALUES (?, ?)",
this.room.getId(), userId);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
}
}
@@ -196,13 +197,11 @@ public class RoomRightsManager {
this.room.sendComposer(new RoomRemoveRightsListComposer(this.room, userId).compose());
if (this.rights.remove(userId)) {
try (Connection connection = Emulator.getDatabase().getDataSource()
.getConnection(); PreparedStatement statement = connection.prepareStatement(
"DELETE FROM room_rights WHERE room_id = ? AND user_id = ?")) {
statement.setInt(1, this.room.getId());
statement.setInt(2, userId);
statement.execute();
} catch (SQLException e) {
try {
SqlQueries.update(
"DELETE FROM room_rights WHERE room_id = ? AND user_id = ?",
this.room.getId(), userId);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
}
}
@@ -225,12 +224,9 @@ public class RoomRightsManager {
this.rights.clear();
try (Connection connection = Emulator.getDatabase().getDataSource()
.getConnection(); PreparedStatement statement = connection.prepareStatement(
"DELETE FROM room_rights WHERE room_id = ?")) {
statement.setInt(1, this.room.getId());
statement.execute();
} catch (SQLException e) {
try {
SqlQueries.update("DELETE FROM room_rights WHERE room_id = ?", this.room.getId());
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
}
@@ -288,25 +284,22 @@ public class RoomRightsManager {
/**
* Gets all users with rights in the room.
*/
public THashMap<Integer, String> getUsersWithRights() {
THashMap<Integer, String> rightsMap = new THashMap<>();
if (!this.rights.isEmpty()) {
try (Connection connection = Emulator.getDatabase().getDataSource()
.getConnection(); PreparedStatement statement = connection.prepareStatement(
"SELECT users.username AS username, users.id as user_id FROM room_rights INNER JOIN users ON room_rights.user_id = users.id WHERE room_id = ?")) {
statement.setInt(1, this.room.getId());
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
rightsMap.put(set.getInt("user_id"), set.getString("username"));
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
public Map<Integer, String> getUsersWithRights() {
if (this.rights.isEmpty()) {
return Collections.emptyMap();
}
return rightsMap;
try {
return SqlQueries.query(
"SELECT users.username AS username, users.id as user_id FROM room_rights INNER JOIN users ON room_rights.user_id = users.id WHERE room_id = ?",
rs -> Map.entry(rs.getInt("user_id"), rs.getString("username")),
this.room.getId())
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b));
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return Collections.emptyMap();
}
}
/**
@@ -1,6 +1,7 @@
package com.eu.habbo.habbohotel.users;
import com.eu.habbo.Emulator;
import com.eu.habbo.database.SqlQueries;
import com.eu.habbo.habbohotel.modtool.ModToolBan;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.permissions.Rank;
@@ -22,6 +23,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -47,37 +49,27 @@ public class HabboManager {
}
public static HabboInfo getOfflineHabboInfo(int id) {
HabboInfo info = null;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE id = ? LIMIT 1")) {
statement.setInt(1, id);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
info = new HabboInfo(set);
}
}
} catch (SQLException e) {
try {
return SqlQueries.queryOne(
"SELECT * FROM users WHERE id = ? LIMIT 1",
HabboInfo::new,
id).orElse(null);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return null;
}
return info;
}
public static HabboInfo getOfflineHabboInfo(String username) {
HabboInfo info = null;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE username = ? LIMIT 1")) {
statement.setString(1, username);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
info = new HabboInfo(set);
}
}
} catch (SQLException e) {
try {
return SqlQueries.queryOne(
"SELECT * FROM users WHERE username = ? LIMIT 1",
HabboInfo::new,
username).orElse(null);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return null;
}
return info;
}
public void addHabbo(Habbo habbo) {
@@ -195,43 +187,32 @@ public class HabboManager {
LOGGER.info("Habbo Manager -> Disposed!");
}
public ArrayList<HabboInfo> getCloneAccounts(Habbo habbo, int limit) {
ArrayList<HabboInfo> habboInfo = new ArrayList<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE (ip_register = ? OR ip_current = ?) AND id != ? ORDER BY id DESC LIMIT ?")) {
statement.setString(1, habbo.getHabboInfo().getIpRegister());
statement.setString(2, habbo.getHabboInfo().getIpLogin());
statement.setInt(3, habbo.getHabboInfo().getId());
statement.setInt(4, limit);
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
habboInfo.add(new HabboInfo(set));
}
}
} catch (SQLException e) {
public List<HabboInfo> getCloneAccounts(Habbo habbo, int limit) {
try {
return SqlQueries.query(
"SELECT * FROM users WHERE (ip_register = ? OR ip_current = ?) AND id != ? ORDER BY id DESC LIMIT ?",
HabboInfo::new,
habbo.getHabboInfo().getIpRegister(),
habbo.getHabboInfo().getIpLogin(),
habbo.getHabboInfo().getId(),
limit);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return new ArrayList<>();
}
return habboInfo;
}
public List<Map.Entry<Integer, String>> getNameChanges(int userId, int limit) {
List<Map.Entry<Integer, String>> nameChanges = new ArrayList<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT timestamp, new_name FROM namechange_log WHERE user_id = ? ORDER by timestamp DESC LIMIT ?")) {
statement.setInt(1, userId);
statement.setInt(2, limit);
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
nameChanges.add(new AbstractMap.SimpleEntry<>(set.getInt("timestamp"), set.getString("new_name")));
}
}
} catch (SQLException e) {
try {
return SqlQueries.query(
"SELECT timestamp, new_name FROM namechange_log WHERE user_id = ? ORDER by timestamp DESC LIMIT ?",
rs -> new AbstractMap.SimpleEntry<>(rs.getInt("timestamp"), rs.getString("new_name")),
userId,
limit);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return Collections.emptyList();
}
return nameChanges;
}
@@ -277,11 +258,9 @@ public class HabboManager {
habbo.getClient().sendResponse(new RecyclerLogicComposer());
habbo.alert(Emulator.getTexts().getValue("commands.generic.cmd_give_rank.new_rank").replace("id", newRank.getName()));
} else {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users SET `rank` = ? WHERE id = ? LIMIT 1")) {
statement.setInt(1, rankId);
statement.setInt(2, userId);
statement.execute();
} catch (SQLException e) {
try {
SqlQueries.update("UPDATE users SET `rank` = ? WHERE id = ? LIMIT 1", rankId, userId);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
}
}
@@ -294,11 +273,9 @@ public class HabboManager {
if (habbo != null) {
habbo.giveCredits(credits);
} else {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users SET credits = credits + ? WHERE id = ? LIMIT 1")) {
statement.setInt(1, credits);
statement.setInt(2, userId);
statement.execute();
} catch (SQLException e) {
try {
SqlQueries.update("UPDATE users SET credits = credits + ? WHERE id = ? LIMIT 1", credits, userId);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
}
}
@@ -14,7 +14,6 @@ import com.eu.habbo.messages.outgoing.catalog.ClubCenterDataComposer;
import com.eu.habbo.messages.outgoing.generic.PickMonthlyClubGiftNotificationComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
import com.eu.habbo.messages.outgoing.users.*;
import gnu.trove.map.hash.THashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,6 +23,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
@@ -215,7 +215,7 @@ public class SubscriptionHabboClub extends Subscription {
}
}
THashMap<String, Object> queryParams = new THashMap<>();
Map<String, Object> queryParams = new HashMap<>();
queryParams.put("@user_id", habbo.getId());
queryParams.put("@timestamp_start", habbo.getHabboStats().lastHCPayday);
queryParams.put("@timestamp_end", HC_PAYDAY_NEXT_DATE);
@@ -4,7 +4,6 @@ import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
import gnu.trove.map.hash.THashMap;
import java.util.Map;
@@ -20,7 +19,7 @@ public class RoomRightsListComposer extends MessageComposer {
this.response.init(Outgoing.RoomRightsListComposer);
this.response.appendInt(this.room.getId());
THashMap<Integer, String> rightsMap = this.room.getUsersWithRights();
Map<Integer, String> rightsMap = this.room.getUsersWithRights();
this.response.appendInt(rightsMap.size());