From 9c62824fad3b592fb25e4286f4433e2664d42d45 Mon Sep 17 00:00:00 2001 From: acrylic-style Date: Thu, 7 Jul 2022 22:10:11 +0900 Subject: [PATCH] Add CheckRankExpirationPacket (but these will change in the future) --- .../api/AziPluginMessaging.java | 12 ++++ .../api/protocol/Protocol.java | 10 ++- .../ProxyboundCheckRankExpirationPacket.java | 35 ++++++++++ .../ServerboundActionResponsePacket.java | 3 +- .../ServerboundCheckRankExpirationPacket.java | 21 ++++++ .../ProxyboundCheckRankExpirationMessage.java | 42 ++++++++++++ ...ServerboundCheckRankExpirationMessage.java | 64 +++++++++++++++++++ .../spigot/AziPluginMessagingSpigot.java | 7 ++ .../spigot/SpigotPlugin.java | 8 +++ .../event/CheckedRankExpirationEvent.java | 34 ++++++++++ .../velocity/AziPluginMessagingVelocity.java | 7 ++ .../velocity/DBConnector.java | 14 ++-- 12 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundCheckRankExpirationPacket.java create mode 100644 api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundCheckRankExpirationPacket.java create mode 100644 api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundCheckRankExpirationMessage.java create mode 100644 api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ServerboundCheckRankExpirationMessage.java create mode 100644 spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/event/CheckedRankExpirationEvent.java diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java index d99e100..3906297 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/AziPluginMessaging.java @@ -3,8 +3,10 @@ import net.azisaba.azipluginmessaging.api.entity.Player; import net.azisaba.azipluginmessaging.api.entity.PlayerAdapter; import net.azisaba.azipluginmessaging.api.protocol.PacketQueue; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundCheckRankExpirationMessage; import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.util.SQLThrowableConsumer; +import net.azisaba.azipluginmessaging.api.util.SQLThrowableFunction; import net.azisaba.azipluginmessaging.api.yaml.YamlObject; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.ApiStatus; @@ -81,6 +83,11 @@ default void loadConfig(@NotNull YamlObject obj) { throw new UnsupportedOperationException("Unsupported in current environment."); } + @Contract + default @NotNull CompletableFuture getPreparedStatement(@Language("SQL") @NotNull String sql, @NotNull SQLThrowableFunction action) { + throw new UnsupportedOperationException("Unsupported in current environment."); + } + @Contract default void checkRankAsync(@NotNull UUID uuid) { throw new UnsupportedOperationException("Unsupported in current environment."); @@ -96,5 +103,10 @@ interface Server { default PacketSender getPacketSender() { throw new UnsupportedOperationException("Unsupported in current environment."); } + + @ApiStatus.Internal + default void handle(@NotNull ServerboundCheckRankExpirationMessage msg) { + throw new UnsupportedOperationException("Unsupported in current environment."); + } } } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java index 31e4682..fd59974 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java @@ -5,6 +5,7 @@ import net.azisaba.azipluginmessaging.api.Logger; import net.azisaba.azipluginmessaging.api.protocol.handler.MessageHandler; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyMessageHandler; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundCheckRankExpirationPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundClearPrefixPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundEncryptionPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundGiveGamingSaraPacket; @@ -18,17 +19,20 @@ import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundToggleSaraShowPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ServerMessageHandler; import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundActionResponsePacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundCheckRankExpirationPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundEncryptionPacket; import net.azisaba.azipluginmessaging.api.protocol.message.EncryptionMessage; import net.azisaba.azipluginmessaging.api.protocol.message.Message; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerMessage; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerWithServerMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundCheckRankExpirationMessage; import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundClearPrefixMessage; import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundGiveNitroSaraMessage; import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundGiveSaraMessage; import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundSetPrefixMessage; import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundSetRankMessage; import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundActionResponseMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundCheckRankExpirationMessage; import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.api.server.ServerConnection; import net.azisaba.azipluginmessaging.api.util.EncryptionUtil; @@ -66,13 +70,15 @@ public final class Protocol, M extends Message> { public static final Protocol P_TOGGLE_GAMING_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x04, new ProxyboundToggleGamingSaraPacket()); public static final Protocol P_TOGGLE_SARA_HIDE = new Protocol<>(PacketFlow.TO_PROXY, 0x05, new ProxyboundToggleSaraHidePacket()); // non-contextual public static final Protocol P_TOGGLE_SARA_SHOW = new Protocol<>(PacketFlow.TO_PROXY, 0x06, new ProxyboundToggleSaraShowPacket()); // contextual - public static final Protocol P_SET_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x07, new ProxyboundSetPrefixPacket()); - public static final Protocol P_CLEAR_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x08, new ProxyboundClearPrefixPacket()); + public static final Protocol P_SET_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x07, new ProxyboundSetPrefixPacket()); // may be contextual + public static final Protocol P_CLEAR_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x08, new ProxyboundClearPrefixPacket()); // may be contextual public static final Protocol P_GIVE_NITRO_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x09, new ProxyboundGiveNitroSaraPacket()); public static final Protocol P_TOGGLE_NITRO_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x0A, new ProxyboundToggleNitroSaraPacket()); + public static final Protocol P_CHECK_RANK_EXPIRATION = new Protocol<>(PacketFlow.TO_PROXY, 0x0B, new ProxyboundCheckRankExpirationPacket()); public static final Protocol S_ENCRYPTION = new Protocol<>(PacketFlow.TO_SERVER, 0x00, new ServerboundEncryptionPacket()); public static final Protocol S_ACTION_RESPONSE = new Protocol<>(PacketFlow.TO_SERVER, 0x01, new ServerboundActionResponsePacket()); + public static final Protocol S_CHECK_RANK_EXPIRATION = new Protocol<>(PacketFlow.TO_SERVER, 0x02, new ServerboundCheckRankExpirationPacket()); private final PacketFlow packetFlow; private final byte id; diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundCheckRankExpirationPacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundCheckRankExpirationPacket.java new file mode 100644 index 0000000..5085670 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundCheckRankExpirationPacket.java @@ -0,0 +1,35 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.AziPluginMessagingProvider; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; +import net.azisaba.azipluginmessaging.api.protocol.message.ProxyboundCheckRankExpirationMessage; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundCheckRankExpirationMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import net.azisaba.azipluginmessaging.api.server.ServerConnection; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; +import java.sql.ResultSet; + +public class ProxyboundCheckRankExpirationPacket implements ProxyMessageHandler { + @Override + public @NotNull ProxyboundCheckRankExpirationMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { + return ProxyboundCheckRankExpirationMessage.read(in); + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull ProxyboundCheckRankExpirationMessage msg) throws Exception { + long expiresAt = AziPluginMessagingProvider.get().getProxy().getPreparedStatement("SELECT `expires_at` FROM `temp_rank` WHERE `player_uuid` = ? AND `rank` = ?", ps -> { + ps.setString(1, msg.getPlayer().getUniqueId().toString()); + ps.setString(2, msg.getRank()); + ResultSet rs = ps.executeQuery(); + if (!rs.next()) { + return -1L; + } + return rs.getLong("expires_at"); + }).join(); + ServerboundCheckRankExpirationMessage message = new ServerboundCheckRankExpirationMessage(msg.getPlayer(), msg.getRank(), expiresAt); + Protocol.S_CHECK_RANK_EXPIRATION.sendPacket(sender, message); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundActionResponsePacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundActionResponsePacket.java index 4574dda..d340d55 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundActionResponsePacket.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundActionResponsePacket.java @@ -10,7 +10,8 @@ import java.util.UUID; /** - * Sends the arbitrary message to a player. + * Sends an arbitrary message to a player. This packet is used for sending messages to a player that is not in the same + * proxy server, supporting the multi-proxy environment. */ public class ServerboundActionResponsePacket implements ServerMessageHandler { @NotNull diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundCheckRankExpirationPacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundCheckRankExpirationPacket.java new file mode 100644 index 0000000..56f3008 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundCheckRankExpirationPacket.java @@ -0,0 +1,21 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.AziPluginMessagingProvider; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundCheckRankExpirationMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; + +public class ServerboundCheckRankExpirationPacket implements ServerMessageHandler { + @Override + public @NotNull ServerboundCheckRankExpirationMessage read(@NotNull DataInputStream in) throws IOException { + return ServerboundCheckRankExpirationMessage.read(in); + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull ServerboundCheckRankExpirationMessage msg) throws Exception { + AziPluginMessagingProvider.get().getServer().handle(msg); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundCheckRankExpirationMessage.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundCheckRankExpirationMessage.java new file mode 100644 index 0000000..2105fb2 --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ProxyboundCheckRankExpirationMessage.java @@ -0,0 +1,42 @@ +package net.azisaba.azipluginmessaging.api.protocol.message; + +import net.azisaba.azipluginmessaging.api.entity.Player; +import net.azisaba.azipluginmessaging.api.entity.SimplePlayer; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Objects; + +@ApiStatus.Internal // this packet WILL change +public class ProxyboundCheckRankExpirationMessage extends PlayerMessage { + private final String rank; + + public ProxyboundCheckRankExpirationMessage(@NotNull Player player, @NotNull String rank) { + super(player); + this.rank = Objects.requireNonNull(rank, "rank"); + } + + /** + * Returns the requested rank. + * @return the rank + */ + @NotNull + public String getRank() { + return rank; + } + + @Override + public void write(@NotNull DataOutputStream out) throws IOException { + super.write(out); + out.writeUTF(rank); + } + + public static @NotNull ProxyboundCheckRankExpirationMessage read(@NotNull DataInputStream in) throws IOException { + Player player = SimplePlayer.read(in); + String rank = in.readUTF(); + return new ProxyboundCheckRankExpirationMessage(player, rank); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ServerboundCheckRankExpirationMessage.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ServerboundCheckRankExpirationMessage.java new file mode 100644 index 0000000..dba088a --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/ServerboundCheckRankExpirationMessage.java @@ -0,0 +1,64 @@ +package net.azisaba.azipluginmessaging.api.protocol.message; + +import net.azisaba.azipluginmessaging.api.entity.Player; +import net.azisaba.azipluginmessaging.api.entity.SimplePlayer; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@ApiStatus.Internal // this packet WILL change +public class ServerboundCheckRankExpirationMessage extends PlayerMessage { + private final String rank; + private final long expiresAt; + + public ServerboundCheckRankExpirationMessage(@NotNull Player player, @NotNull String rank, long expiresAt) { + super(player); + this.rank = Objects.requireNonNull(rank, "rank"); + this.expiresAt = expiresAt; + } + + /** + * Returns the rank which the server has requested. + * @return the rank + */ + @NotNull + public String getRank() { + return rank; + } + + /** + * Returns the expiration time of the player's rank in milliseconds.
+ * Special cases:
+ *
    + *
  • Returns 0 if the rank never expires
  • + *
  • Returns -1 if the player doesn't have the temporary rank.
  • + *
+ * @return expiration time + */ + public long getExpiresAt() { + return expiresAt; + } + + public long getExpiresAt(@NotNull TimeUnit unit) { + return unit.convert(expiresAt, TimeUnit.MILLISECONDS); + } + + @Override + public void write(@NotNull DataOutputStream out) throws IOException { + super.write(out); + out.writeUTF(rank); + out.writeLong(expiresAt); + } + + public static @NotNull ServerboundCheckRankExpirationMessage read(@NotNull DataInputStream in) throws IOException { + Player player = SimplePlayer.read(in); + String rank = in.readUTF(); + long expiresAt = in.readLong(); + return new ServerboundCheckRankExpirationMessage(player, rank, expiresAt); + } +} diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java index 8b45a45..2a45a69 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/AziPluginMessagingSpigot.java @@ -5,8 +5,10 @@ import net.azisaba.azipluginmessaging.api.Logger; import net.azisaba.azipluginmessaging.api.entity.PlayerAdapter; import net.azisaba.azipluginmessaging.api.protocol.PacketQueue; +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundCheckRankExpirationMessage; import net.azisaba.azipluginmessaging.api.server.PacketSender; import net.azisaba.azipluginmessaging.spigot.entity.PlayerImpl; +import net.azisaba.azipluginmessaging.spigot.event.CheckedRankExpirationEvent; import net.azisaba.azipluginmessaging.spigot.protocol.SimplePacketQueue; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -89,5 +91,10 @@ public static class ServerImpl implements Server { } catch (RuntimeException ignored) {} return null; } + + @Override + public void handle(@NotNull ServerboundCheckRankExpirationMessage msg) { + Bukkit.getPluginManager().callEvent(new CheckedRankExpirationEvent(msg)); + } } } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java index 63a8726..634dd1d 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/SpigotPlugin.java @@ -24,6 +24,7 @@ public class SpigotPlugin extends JavaPlugin implements Listener { public static SpigotPlugin plugin; + private boolean processJoinEvent = false; @Override public void onLoad() { @@ -35,6 +36,9 @@ public void onLoad() { @Override public void onEnable() { + // Don't process PlayerJoinEvent for 20 ticks, the server may be lagging at this point. + Bukkit.getScheduler().runTaskLater(this, () -> processJoinEvent = true, 20); + Objects.requireNonNull(Bukkit.getPluginCommand("azipluginmessaging")).setExecutor(new AziPluginMessagingCommand()); try { Bukkit.getMessenger().registerOutgoingPluginChannel(this, Protocol.LEGACY_CHANNEL_ID); @@ -66,6 +70,10 @@ public static PacketSender getAnyPacketSenderOrNull() { @EventHandler public void onPlayerJoin(PlayerJoinEvent e) { + if (!processJoinEvent) { + // < 20 ticks + return; + } PlayerImpl player = PlayerImpl.of(e.getPlayer()); player.setEncrypted(false); player.setRemotePublicKeyInternal(null); diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/event/CheckedRankExpirationEvent.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/event/CheckedRankExpirationEvent.java new file mode 100644 index 0000000..874b399 --- /dev/null +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/event/CheckedRankExpirationEvent.java @@ -0,0 +1,34 @@ +package net.azisaba.azipluginmessaging.spigot.event; + +import net.azisaba.azipluginmessaging.api.protocol.message.ServerboundCheckRankExpirationMessage; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +public class CheckedRankExpirationEvent extends Event { + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final ServerboundCheckRankExpirationMessage msg; + + public CheckedRankExpirationEvent(@NotNull ServerboundCheckRankExpirationMessage msg) { + this.msg = msg; + } + + @NotNull + public ServerboundCheckRankExpirationMessage getMsg() { + return msg; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + @SuppressWarnings("unused") // used by spigot + @Contract(pure = true) + @NotNull + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java index 3f1ae3b..485d69c 100644 --- a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java +++ b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/AziPluginMessagingVelocity.java @@ -10,6 +10,7 @@ import net.azisaba.azipluginmessaging.api.protocol.PacketQueue; import net.azisaba.azipluginmessaging.api.util.LuckPermsUtil; import net.azisaba.azipluginmessaging.api.util.SQLThrowableConsumer; +import net.azisaba.azipluginmessaging.api.util.SQLThrowableFunction; import net.azisaba.azipluginmessaging.api.yaml.YamlObject; import net.azisaba.azipluginmessaging.velocity.entity.PlayerImpl; import net.luckperms.api.LuckPerms; @@ -106,6 +107,12 @@ public void loadConfig(@NotNull YamlObject obj) { return DBConnector.runPreparedStatement(sql, action); } + @Contract + @Override + public @NotNull CompletableFuture getPreparedStatement(@NotNull String sql, @NotNull SQLThrowableFunction action) { + return DBConnector.getPreparedStatement(sql, action); + } + @Override public void checkRankAsync(@NotNull UUID uuid) { DBConnector.runPreparedStatement("SELECT `rank`, `expires_at`, `clear_prefix_on_expire` FROM `temp_rank` WHERE `player_uuid` = ? AND `expires_at` > 0 AND `expires_at` < CURRENT_TIMESTAMP", ps -> { diff --git a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/DBConnector.java b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/DBConnector.java index 49296aa..582b57f 100644 --- a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/DBConnector.java +++ b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/DBConnector.java @@ -93,10 +93,16 @@ public static void use(@NotNull SQLThrowableConsumer action) throws } @Contract(pure = true) - public static R getPreparedStatement(@Language("SQL") @NotNull String sql, @NotNull SQLThrowableFunction action) throws SQLException { - return use(connection -> { - try (PreparedStatement statement = connection.prepareStatement(sql)) { - return action.apply(statement); + public static @NotNull CompletableFuture getPreparedStatement(@Language("SQL") @NotNull String sql, @NotNull SQLThrowableFunction action) { + return CompletableFuture.supplyAsync(() -> { + try { + return use(connection -> { + try (PreparedStatement statement = connection.prepareStatement(sql)) { + return action.apply(statement); + } + }); + } catch (SQLException e) { + throw new RuntimeException(e); } }); }