diff --git a/api/src/main/java/com/alttd/chat/database/ChatLogQueries.java b/api/src/main/java/com/alttd/chat/database/ChatLogQueries.java index 3185e48..0c865d9 100644 --- a/api/src/main/java/com/alttd/chat/database/ChatLogQueries.java +++ b/api/src/main/java/com/alttd/chat/database/ChatLogQueries.java @@ -40,35 +40,124 @@ public class ChatLogQueries { } } - public static @NotNull CompletableFuture storeMessages(HashMap> chatMessages) { - String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, type, channel, receiver, chat_message, mini_message, blocked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; - return CompletableFuture.supplyAsync(() -> { - try (Connection connection = DatabaseConnection.createTransactionConnection()) { - PreparedStatement preparedStatement = connection.prepareStatement(insertQuery); - for (List chatLogList : chatMessages.values()) { - for (ChatLog chatLog : chatLogList) { - chatLog.prepareStatement(preparedStatement); - preparedStatement.addBatch(); - } - } - int[] updatedRowsCount = preparedStatement.executeBatch(); - boolean isSuccess = Arrays.stream(updatedRowsCount).allMatch(i -> i >= 0); + public static @NotNull CompletableFuture storeMessages( + HashMap> chatMessages + ) { + String insertQuery = """ + INSERT INTO chat_log + (uuid, time_stamp, server, type, channel, receiver, + chat_message, mini_message, blocked) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """; + + return CompletableFuture.supplyAsync(() -> { + try (Connection connection = DatabaseConnection.createTransactionConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(insertQuery)) { + + try { + addMessagesToBatch(chatMessages, preparedStatement); + + int[] updatedRowsCount = preparedStatement.executeBatch(); + + boolean isSuccess = Arrays.stream(updatedRowsCount) + .allMatch(i -> i >= 0 || i == Statement.SUCCESS_NO_INFO); + + if (isSuccess) { + connection.commit(); + return true; + } - if (isSuccess) { - connection.commit(); - return true; - } else { connection.rollback(); - ALogger.warn("Failed to store messages"); - return false; + } catch (BatchUpdateException exception) { + connection.rollback(); + + ALogger.error( + "Failed to store chat batch. Trying messages individually.", + exception + ); } - } catch (SQLException sqlException) { - ALogger.error("Failed to store chat messages", sqlException); - throw new CompletionException("Failed to store chat messages", sqlException); + + return storeMessagesIndividually( + connection, + preparedStatement, + chatMessages + ); + + } catch (SQLException exception) { + ALogger.error("Failed to store chat messages", exception); + throw new CompletionException( + "Failed to store chat messages", + exception + ); } }); } + private static void addMessagesToBatch( + HashMap> chatMessages, + PreparedStatement preparedStatement + ) throws SQLException { + for (List chatLogList : chatMessages.values()) { + for (ChatLog chatLog : chatLogList) { + chatLog.prepareStatement(preparedStatement); + preparedStatement.addBatch(); + } + } + } + + private static boolean storeMessagesIndividually( + Connection connection, + PreparedStatement preparedStatement, + HashMap> chatMessages + ) throws SQLException { + boolean storedAny = false; + + for (List chatLogList : chatMessages.values()) { + Iterator iterator = chatLogList.iterator(); + + while (iterator.hasNext()) { + ChatLog chatLog = iterator.next(); + + if (storeMessage(preparedStatement, chatLog)) { + storedAny = true; + } else { + iterator.remove(); + } + } + } + + connection.commit(); + + return storedAny; + } + + private static boolean storeMessage( + PreparedStatement preparedStatement, + ChatLog chatLog + ) { + try { + preparedStatement.clearParameters(); + + chatLog.prepareStatement(preparedStatement); + preparedStatement.executeUpdate(); + + return true; + } catch (SQLException exception) { + logInvalidMessage(chatLog, exception); + return false; + } + } + + private static void logInvalidMessage( + ChatLog chatLog, + SQLException exception + ) { + ALogger.error( + "Failed to store chat message. Removing offending message: " + chatLog, + exception + ); + } + public static @NotNull CompletableFuture> retrieveMessages(ChatLogHandler chatLogHandler, UUID uuid, Duration duration, String server) { String query = "SELECT * FROM chat_log WHERE uuid = ? AND time_stamp > ? AND server = ? AND type = 'public'"; return CompletableFuture.supplyAsync(() -> { diff --git a/api/src/main/java/com/alttd/chat/objects/chat_log/ChatLog.java b/api/src/main/java/com/alttd/chat/objects/chat_log/ChatLog.java index ddc9257..dfdf137 100644 --- a/api/src/main/java/com/alttd/chat/objects/chat_log/ChatLog.java +++ b/api/src/main/java/com/alttd/chat/objects/chat_log/ChatLog.java @@ -43,4 +43,19 @@ public class ChatLog implements BatchInsertable { ); preparedStatement.setInt(9, blocked ? 1 : 0); } + + @Override + public String toString() { + return "ChatLog{" + + "uuid=" + uuid + + ", timestamp=" + timestamp + + ", server='" + server + '\'' + + ", type=" + type.name() + + ", channel='" + channel + '\'' + + ", receiver='" + receiver + '\'' + + ", message='" + message + '\'' + + ", miniMessage=" + (miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage)) + + ", blocked=" + blocked + + '}'; + } }