From 8d3660b5086d3cec15732a48969834d8e0e69fa2 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Mon, 22 Dec 2025 00:39:06 +0900 Subject: [PATCH 1/9] Added maxPacketSize property --- .../scala/ldbc/connector/Connection.scala | 10 ++++++++ .../scala/ldbc/connector/MySQLConfig.scala | 8 +++++- .../ldbc/connector/MySQLDataSource.scala | 10 +++++++- .../exception/PacketTooBigException.scala | 15 +++++++++++ .../ldbc/connector/net/PacketSocket.scala | 25 ++++++++++++++++--- .../scala/ldbc/connector/net/Protocol.scala | 3 ++- 6 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala index aca2f00fa..9ee86e0cd 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala @@ -77,6 +77,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]] @@ -93,6 +94,7 @@ object Connection: allowPublicKeyRetrieval, useCursorFetch, useServerPrepStmts, + maxAllowedPacket, databaseTerm, defaultAuthenticationPlugin, plugins, @@ -115,6 +117,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]] @@ -131,6 +134,7 @@ object Connection: allowPublicKeyRetrieval, useCursorFetch, useServerPrepStmts, + maxAllowedPacket, databaseTerm, defaultAuthenticationPlugin, plugins, @@ -151,6 +155,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]], @@ -176,6 +181,7 @@ object Connection: allowPublicKeyRetrieval, useCursorFetch, useServerPrepStmts, + maxAllowedPacket, databaseTerm, defaultAuthenticationPlugin, plugins, @@ -197,6 +203,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = None, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: List[AuthenticationPlugin[F]], @@ -219,6 +226,7 @@ object Connection: allowPublicKeyRetrieval, readTimeout, capabilityFlags, + maxAllowedPacket, defaultAuthenticationPlugin, pluginMap ) @@ -260,6 +268,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = None, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: List[AuthenticationPlugin[F]], @@ -290,6 +299,7 @@ object Connection: allowPublicKeyRetrieval, useCursorFetch, useServerPrepStmts, + maxAllowedPacket, databaseTerm, defaultAuthenticationPlugin, plugins, diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala index 9cab703ae..1457ba885 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala @@ -415,6 +415,10 @@ trait MySQLConfig: * @return a new MySQLConfig with the updated setting */ def setPoolName(name: String): MySQLConfig + + def maxAllowedPacket: Int + + def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig /** * Companion object for MySQLConfig providing factory methods. @@ -455,7 +459,8 @@ object MySQLConfig: connectionTestQuery: Option[String] = None, logPoolState: Boolean = false, poolStateLogInterval: FiniteDuration = 30.seconds, - poolName: String = "ldbc-pool" + poolName: String = "ldbc-pool", + maxAllowedPacket: Int = 65535 ) extends MySQLConfig: override def setHost(host: String): MySQLConfig = copy(host = host) @@ -491,6 +496,7 @@ object MySQLConfig: override def setLogPoolState(enabled: Boolean): MySQLConfig = copy(logPoolState = enabled) override def setPoolStateLogInterval(interval: FiniteDuration): MySQLConfig = copy(poolStateLogInterval = interval) override def setPoolName(name: String): MySQLConfig = copy(poolName = name) + override def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig = copy(maxAllowedPacket = maxAllowedPacket) /** * Creates a default MySQLConfig with standard connection parameters. diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala index 70b4876cd..3f6101e61 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala @@ -84,6 +84,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen tracer: Option[Tracer[F]] = None, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, + maxAllowedPacket: Int = 65535, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]], before: Option[Connection[F] => F[A]] = None, @@ -118,6 +119,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen allowPublicKeyRetrieval = allowPublicKeyRetrieval, useCursorFetch = useCursorFetch, useServerPrepStmts = useServerPrepStmts, + maxAllowedPacket = maxAllowedPacket, databaseTerm = databaseTerm, defaultAuthenticationPlugin = defaultAuthenticationPlugin, plugins = plugins @@ -138,6 +140,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen allowPublicKeyRetrieval = allowPublicKeyRetrieval, useCursorFetch = useCursorFetch, useServerPrepStmts = useServerPrepStmts, + maxAllowedPacket = maxAllowedPacket, databaseTerm = databaseTerm, defaultAuthenticationPlugin = defaultAuthenticationPlugin, plugins = plugins @@ -156,6 +159,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen allowPublicKeyRetrieval = allowPublicKeyRetrieval, useCursorFetch = useCursorFetch, useServerPrepStmts = useServerPrepStmts, + maxAllowedPacket = maxAllowedPacket, databaseTerm = databaseTerm, defaultAuthenticationPlugin = defaultAuthenticationPlugin, plugins = plugins @@ -255,6 +259,9 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen */ def setUseServerPrepStmts(newUseServerPrepStmts: Boolean): MySQLDataSource[F, A] = copy(useServerPrepStmts = newUseServerPrepStmts) + + def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLDataSource[F, A] = + copy(maxAllowedPacket = maxAllowedPacket) /** Sets whether to authentication plugin to be used first for communication with the server. * @param defaultAuthenticationPlugin @@ -389,7 +396,8 @@ object MySQLDataSource: allowPublicKeyRetrieval = config.allowPublicKeyRetrieval, databaseTerm = config.databaseTerm, useCursorFetch = config.useCursorFetch, - useServerPrepStmts = config.useServerPrepStmts + useServerPrepStmts = config.useServerPrepStmts, + maxAllowedPacket = config.maxAllowedPacket ) /** diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala new file mode 100644 index 000000000..c550f9fe2 --- /dev/null +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2023-2025 by Takahiko Tominaga + * This software is licensed under the MIT License (MIT). + * For more information see LICENSE or https://opensource.org/licenses/MIT + */ + +package ldbc.connector.exception + +class PacketTooBigException( + packetLength: Int, + maxAllowed: Int +) extends SQLException( + s"Packet for query is too large ($packetLength > $maxAllowed). " + + s"You can change the value by setting the 'maxAllowedPacket' configuration." +) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala index 4a4fcc2cc..850bed4c6 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala @@ -24,6 +24,7 @@ import ldbc.connector.data.CapabilitiesFlags import ldbc.connector.net.packet.* import ldbc.connector.net.packet.response.InitialPacket import ldbc.connector.net.protocol.parseHeader +import ldbc.connector.exception.PacketTooBigException /** * A higher-level `BitVectorSocket` that speaks in terms of `Packet`. @@ -41,10 +42,15 @@ trait PacketSocket[F[_]]: object PacketSocket: + val DEFAULT_MAX_PACKET_SIZE = 65535 // 64KB (JDBC Driver default) + val PROTOCOL_MAX_PACKET_SIZE = 16777215 // 16MB (MySQL protocol limit) + val MIN_PACKET_SIZE = 0 + def fromBitVectorSocket[F[_]: Concurrent: Console]( bvs: BitVectorSocket[F], debugEnabled: Boolean, - sequenceIdRef: Ref[F, Byte] + sequenceIdRef: Ref[F, Byte], + maxAllowedPacket: Int ): PacketSocket[F] = new PacketSocket[F]: private def debug(msg: => String): F[Unit] = @@ -56,6 +62,7 @@ object PacketSocket: (for header <- bvs.read(4) payloadSize = parseHeader(header.toByteArray) + _ <- validatePacketSize(payloadSize) payload <- bvs.read(payloadSize) response = decoder.decodeValue(payload).require _ <- @@ -94,6 +101,17 @@ object PacketSocket: _ <- sequenceIdRef.update(sequenceId => ((sequenceId + 1) % 256).toByte) yield () + private def validatePacketSize(size: Int): F[Unit] = + if size < MIN_PACKET_SIZE then + Concurrent[F].raiseError( + PacketTooBigException(size, maxAllowedPacket) + ) + else if size > maxAllowedPacket then + Concurrent[F].raiseError( + PacketTooBigException(size, maxAllowedPacket) + ) + else Concurrent[F].unit + def apply[F[_]: Console: Temporal]( debug: Boolean, sockets: Resource[F, Socket[F]], @@ -101,8 +119,9 @@ object PacketSocket: sequenceIdRef: Ref[F, Byte], initialPacketRef: Ref[F, Option[InitialPacket]], readTimeout: Duration, - capabilitiesFlags: Set[CapabilitiesFlags] + capabilitiesFlags: Set[CapabilitiesFlags], + maxAllowedPacket: Int ): Resource[F, PacketSocket[F]] = BitVectorSocket(sockets, sequenceIdRef, initialPacketRef, sslOptions, readTimeout, capabilitiesFlags).map( - fromBitVectorSocket(_, debug, sequenceIdRef) + fromBitVectorSocket(_, debug, sequenceIdRef, maxAllowedPacket) ) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala index 701e8346d..f71e16504 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala @@ -583,6 +583,7 @@ object Protocol: allowPublicKeyRetrieval: Boolean = false, readTimeout: Duration, capabilitiesFlags: Set[CapabilitiesFlags], + maxAllowedPacket: Int, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: Map[String, AuthenticationPlugin[F]] ): Resource[F, Protocol[F]] = @@ -590,7 +591,7 @@ object Protocol: sequenceIdRef <- Resource.eval(Ref[F].of[Byte](0x01)) initialPacketRef <- Resource.eval(Ref[F].of[Option[InitialPacket]](None)) packetSocket <- - PacketSocket[F](debug, sockets, sslOptions, sequenceIdRef, initialPacketRef, readTimeout, capabilitiesFlags) + PacketSocket[F](debug, sockets, sslOptions, sequenceIdRef, initialPacketRef, readTimeout, capabilitiesFlags, maxAllowedPacket) protocol <- Resource.eval( fromPacketSocket( packetSocket, From 0e678fb74f025e02542e2f3114d3e69ca4cd9d7b Mon Sep 17 00:00:00 2001 From: takapi327 Date: Mon, 22 Dec 2025 00:44:19 +0900 Subject: [PATCH 2/9] Added scaladoc comment --- .../scala/ldbc/connector/MySQLConfig.scala | 39 ++++++++++++++++++- .../ldbc/connector/MySQLDataSource.scala | 8 +++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala index 1457ba885..0fc24bbf9 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala @@ -415,9 +415,46 @@ trait MySQLConfig: * @return a new MySQLConfig with the updated setting */ def setPoolName(name: String): MySQLConfig - + + /** + * Gets the maximum allowed packet size for network communication with MySQL server. + * + * This setting controls the maximum size of packets that can be sent to or received from + * the MySQL server. It helps prevent memory exhaustion attacks and ensures compatibility + * with the MySQL protocol limits. + * + * The value corresponds to the MySQL server's `max_allowed_packet` system variable. + * + * @return the maximum packet size in bytes + */ def maxAllowedPacket: Int + /** + * Sets the maximum allowed packet size for network communication. + * + * This setting provides protection against: + * - Memory exhaustion attacks through oversized packets + * - Denial of Service (DoS) attacks via large data payloads + * - Accidental transmission of extremely large data sets + * + * @param maxAllowedPacket the maximum packet size in bytes + * @return a new MySQLConfig with the updated setting + * + * @example {{{ + * // Set conservative 64KB limit (default) + * config.setMaxAllowedPacket(65535) + * + * // Set practical 1MB limit for applications with moderate BLOB usage + * config.setMaxAllowedPacket(1048576) + * + * // Set maximum protocol limit for applications requiring large data transfers + * config.setMaxAllowedPacket(16777215) + * }}} + * + * @note The default value of 65,535 bytes (64KB) is compatible with MySQL JDBC Driver defaults + * and provides good security against packet-based attacks while accommodating most use cases. + * @see [[https://dev.mysql.com/doc/refman/en/packet-too-large.html MySQL Protocol Packet Limits]] + */ def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig /** diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala index 3f6101e61..2cbfa0b43 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala @@ -48,6 +48,7 @@ import ldbc.DataSource * @param tracer optional OpenTelemetry tracer for distributed tracing * @param useCursorFetch whether to use cursor-based fetching for result sets * @param useServerPrepStmts whether to use server-side prepared statements + * @param maxAllowedPacket Maximum allowed packet size for network communication in bytes. * @param defaultAuthenticationPlugin The authentication plugin used first for communication with the server * @param plugins Additional authentication plugins used for communication with the server * @param before optional hook to execute before a connection is acquired @@ -259,7 +260,12 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen */ def setUseServerPrepStmts(newUseServerPrepStmts: Boolean): MySQLDataSource[F, A] = copy(useServerPrepStmts = newUseServerPrepStmts) - + + /** Sets the maximum allowed packet size for network communication. + * + * @param maxAllowedPacket the maximum packet size in bytes (0 to 16,777,215) + * @return a new MySQLDataSource with the updated packet size limit + */ def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLDataSource[F, A] = copy(maxAllowedPacket = maxAllowedPacket) From 12b2ffa4b628efc025101be720584cb659823832 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Mon, 22 Dec 2025 00:44:44 +0900 Subject: [PATCH 3/9] Added scaladoc comment --- .../exception/PacketTooBigException.scala | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala index c550f9fe2..cb7ddd734 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala @@ -6,6 +6,56 @@ package ldbc.connector.exception +/** + * Exception thrown when a MySQL protocol packet exceeds the configured maximum packet size limit. + * + * This exception serves as a security mechanism to prevent memory exhaustion attacks and ensures + * compatibility with MySQL protocol constraints. It is thrown when the connector attempts to + * send or receive a packet that exceeds the `maxAllowedPacket` configuration setting. + * + * The `maxAllowedPacket` setting corresponds to MySQL's `max_allowed_packet` system variable + * and provides protection against: + * + * - **Memory exhaustion attacks**: Prevents attackers from sending oversized packets to consume server memory + * - **Denial of Service (DoS)**: Blocks attempts to overwhelm the system with large data payloads + * - **Accidental large data transfers**: Catches unintentional transmission of extremely large datasets + * - **Protocol violations**: Enforces MySQL protocol packet size constraints (max 16MB) + * + * @param packetLength the actual size of the packet that exceeded the limit, in bytes + * @param maxAllowed the configured maximum packet size limit, in bytes + * + * @example {{{ + * // Typical usage when packet size validation fails + * try { + * // Some operation that sends a large packet + * connection.executeUpdate(queryWithLargeData) + * } catch { + * case ex: PacketTooBigException => + * println(s"Packet too large: ${ex.getMessage}") + * // Consider increasing maxAllowedPacket or reducing data size + * } + * }}} + * + * @example {{{ + * // Configure larger packet size to handle bigger data + * val config = MySQLConfig.default + * .setMaxAllowedPacket(1048576) // 1MB limit + * + * // Or use MySQL protocol maximum + * val config = MySQLConfig.default + * .setMaxAllowedPacket(16777215) // 16MB - protocol maximum + * }}} + * + * @see [[ldbc.connector.MySQLConfig.maxAllowedPacket]] for configuration details + * @see [[ldbc.connector.MySQLConfig.setMaxAllowedPacket]] for setting the packet size limit + * @see [[https://dev.mysql.com/doc/refman/en/packet-too-large.html MySQL Protocol Packet Limits]] + * + * @note The default packet size limit is 65,535 bytes (64KB), which matches MySQL JDBC Driver defaults + * and provides good security against packet-based attacks while accommodating most use cases. + * + * @note This exception extends SQLException to maintain compatibility with JDBC error handling patterns. + * The error message includes both the actual packet size and the configured limit for debugging. + */ class PacketTooBigException( packetLength: Int, maxAllowed: Int From 6d161cbba3e1c057c20c6fbe0fd0ce317b783363 Mon Sep 17 00:00:00 2001 From: takapi327 Date: Mon, 22 Dec 2025 00:45:06 +0900 Subject: [PATCH 4/9] Action sbt scalafmtAll --- .../main/scala/ldbc/connector/Connection.scala | 12 ++++++------ .../main/scala/ldbc/connector/MySQLConfig.scala | 4 ++-- .../scala/ldbc/connector/MySQLDataSource.scala | 10 +++++----- .../exception/PacketTooBigException.scala | 8 ++++---- .../scala/ldbc/connector/net/PacketSocket.scala | 16 ++++++++-------- .../main/scala/ldbc/connector/net/Protocol.scala | 13 +++++++++++-- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala index 9ee86e0cd..ea81e4a10 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala @@ -77,7 +77,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]] @@ -117,7 +117,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]] @@ -155,7 +155,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]], @@ -181,7 +181,7 @@ object Connection: allowPublicKeyRetrieval, useCursorFetch, useServerPrepStmts, - maxAllowedPacket, + maxAllowedPacket, databaseTerm, defaultAuthenticationPlugin, plugins, @@ -203,7 +203,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = None, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: List[AuthenticationPlugin[F]], @@ -268,7 +268,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = 65535, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = None, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: List[AuthenticationPlugin[F]], diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala index 0fc24bbf9..df91818a2 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala @@ -428,7 +428,7 @@ trait MySQLConfig: * @return the maximum packet size in bytes */ def maxAllowedPacket: Int - + /** * Sets the maximum allowed packet size for network communication. * @@ -497,7 +497,7 @@ object MySQLConfig: logPoolState: Boolean = false, poolStateLogInterval: FiniteDuration = 30.seconds, poolName: String = "ldbc-pool", - maxAllowedPacket: Int = 65535 + maxAllowedPacket: Int = 65535 ) extends MySQLConfig: override def setHost(host: String): MySQLConfig = copy(host = host) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala index 2cbfa0b43..61d70572c 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala @@ -85,7 +85,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen tracer: Option[Tracer[F]] = None, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = 65535, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]], before: Option[Connection[F] => F[A]] = None, @@ -120,7 +120,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen allowPublicKeyRetrieval = allowPublicKeyRetrieval, useCursorFetch = useCursorFetch, useServerPrepStmts = useServerPrepStmts, - maxAllowedPacket = maxAllowedPacket, + maxAllowedPacket = maxAllowedPacket, databaseTerm = databaseTerm, defaultAuthenticationPlugin = defaultAuthenticationPlugin, plugins = plugins @@ -141,7 +141,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen allowPublicKeyRetrieval = allowPublicKeyRetrieval, useCursorFetch = useCursorFetch, useServerPrepStmts = useServerPrepStmts, - maxAllowedPacket = maxAllowedPacket, + maxAllowedPacket = maxAllowedPacket, databaseTerm = databaseTerm, defaultAuthenticationPlugin = defaultAuthenticationPlugin, plugins = plugins @@ -160,7 +160,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen allowPublicKeyRetrieval = allowPublicKeyRetrieval, useCursorFetch = useCursorFetch, useServerPrepStmts = useServerPrepStmts, - maxAllowedPacket = maxAllowedPacket, + maxAllowedPacket = maxAllowedPacket, databaseTerm = databaseTerm, defaultAuthenticationPlugin = defaultAuthenticationPlugin, plugins = plugins @@ -403,7 +403,7 @@ object MySQLDataSource: databaseTerm = config.databaseTerm, useCursorFetch = config.useCursorFetch, useServerPrepStmts = config.useServerPrepStmts, - maxAllowedPacket = config.maxAllowedPacket + maxAllowedPacket = config.maxAllowedPacket ) /** diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala index cb7ddd734..7360e1c1f 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/exception/PacketTooBigException.scala @@ -58,8 +58,8 @@ package ldbc.connector.exception */ class PacketTooBigException( packetLength: Int, - maxAllowed: Int + maxAllowed: Int ) extends SQLException( - s"Packet for query is too large ($packetLength > $maxAllowed). " + - s"You can change the value by setting the 'maxAllowedPacket' configuration." -) + s"Packet for query is too large ($packetLength > $maxAllowed). " + + s"You can change the value by setting the 'maxAllowedPacket' configuration." + ) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala index 850bed4c6..0714e97be 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/PacketSocket.scala @@ -21,10 +21,10 @@ import fs2.io.net.Socket import fs2.Chunk import ldbc.connector.data.CapabilitiesFlags +import ldbc.connector.exception.PacketTooBigException import ldbc.connector.net.packet.* import ldbc.connector.net.packet.response.InitialPacket import ldbc.connector.net.protocol.parseHeader -import ldbc.connector.exception.PacketTooBigException /** * A higher-level `BitVectorSocket` that speaks in terms of `Packet`. @@ -42,14 +42,14 @@ trait PacketSocket[F[_]]: object PacketSocket: - val DEFAULT_MAX_PACKET_SIZE = 65535 // 64KB (JDBC Driver default) + val DEFAULT_MAX_PACKET_SIZE = 65535 // 64KB (JDBC Driver default) val PROTOCOL_MAX_PACKET_SIZE = 16777215 // 16MB (MySQL protocol limit) - val MIN_PACKET_SIZE = 0 + val MIN_PACKET_SIZE = 0 def fromBitVectorSocket[F[_]: Concurrent: Console]( - bvs: BitVectorSocket[F], - debugEnabled: Boolean, - sequenceIdRef: Ref[F, Byte], + bvs: BitVectorSocket[F], + debugEnabled: Boolean, + sequenceIdRef: Ref[F, Byte], maxAllowedPacket: Int ): PacketSocket[F] = new PacketSocket[F]: @@ -62,7 +62,7 @@ object PacketSocket: (for header <- bvs.read(4) payloadSize = parseHeader(header.toByteArray) - _ <- validatePacketSize(payloadSize) + _ <- validatePacketSize(payloadSize) payload <- bvs.read(payloadSize) response = decoder.decodeValue(payload).require _ <- @@ -120,7 +120,7 @@ object PacketSocket: initialPacketRef: Ref[F, Option[InitialPacket]], readTimeout: Duration, capabilitiesFlags: Set[CapabilitiesFlags], - maxAllowedPacket: Int + maxAllowedPacket: Int ): Resource[F, PacketSocket[F]] = BitVectorSocket(sockets, sequenceIdRef, initialPacketRef, sslOptions, readTimeout, capabilitiesFlags).map( fromBitVectorSocket(_, debug, sequenceIdRef, maxAllowedPacket) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala index f71e16504..052aa59cb 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/net/Protocol.scala @@ -583,7 +583,7 @@ object Protocol: allowPublicKeyRetrieval: Boolean = false, readTimeout: Duration, capabilitiesFlags: Set[CapabilitiesFlags], - maxAllowedPacket: Int, + maxAllowedPacket: Int, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: Map[String, AuthenticationPlugin[F]] ): Resource[F, Protocol[F]] = @@ -591,7 +591,16 @@ object Protocol: sequenceIdRef <- Resource.eval(Ref[F].of[Byte](0x01)) initialPacketRef <- Resource.eval(Ref[F].of[Option[InitialPacket]](None)) packetSocket <- - PacketSocket[F](debug, sockets, sslOptions, sequenceIdRef, initialPacketRef, readTimeout, capabilitiesFlags, maxAllowedPacket) + PacketSocket[F]( + debug, + sockets, + sslOptions, + sequenceIdRef, + initialPacketRef, + readTimeout, + capabilitiesFlags, + maxAllowedPacket + ) protocol <- Resource.eval( fromPacketSocket( packetSocket, From 14b22859bc4bc30e65434cc29c033fb58f92b05d Mon Sep 17 00:00:00 2001 From: takapi327 Date: Thu, 25 Dec 2025 19:28:23 +0900 Subject: [PATCH 5/9] Added max packet size range check --- .../scala/ldbc/connector/MySQLConfig.scala | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala index df91818a2..fd04508e7 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala @@ -439,6 +439,7 @@ trait MySQLConfig: * * @param maxAllowedPacket the maximum packet size in bytes * @return a new MySQLConfig with the updated setting + * @throws IllegalArgumentException if the value is outside the valid range (1024 to 16,777,215) * * @example {{{ * // Set conservative 64KB limit (default) @@ -453,6 +454,7 @@ trait MySQLConfig: * * @note The default value of 65,535 bytes (64KB) is compatible with MySQL JDBC Driver defaults * and provides good security against packet-based attacks while accommodating most use cases. + * @note Valid range: 1,024 bytes (1KB) minimum to 16,777,215 bytes (16MB) maximum (MySQL protocol limit) * @see [[https://dev.mysql.com/doc/refman/en/packet-too-large.html MySQL Protocol Packet Limits]] */ def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig @@ -462,6 +464,15 @@ trait MySQLConfig: */ object MySQLConfig: + /** Minimum allowed packet size in bytes (1KB) */ + val MIN_PACKET_SIZE: Int = 1024 + + /** Maximum allowed packet size in bytes (16MB - MySQL protocol limit) */ + val MAX_PACKET_SIZE: Int = 16777215 + + /** Default packet size in bytes (64KB - MySQL JDBC Driver compatible) */ + val DEFAULT_PACKET_SIZE: Int = 65535 + /** Default socket options applied to all connections. */ private[ldbc] val defaultSocketOptions: List[SocketOption] = List(SocketOption.noDelay(true)) @@ -497,7 +508,7 @@ object MySQLConfig: logPoolState: Boolean = false, poolStateLogInterval: FiniteDuration = 30.seconds, poolName: String = "ldbc-pool", - maxAllowedPacket: Int = 65535 + maxAllowedPacket: Int = DEFAULT_PACKET_SIZE ) extends MySQLConfig: override def setHost(host: String): MySQLConfig = copy(host = host) @@ -533,7 +544,11 @@ object MySQLConfig: override def setLogPoolState(enabled: Boolean): MySQLConfig = copy(logPoolState = enabled) override def setPoolStateLogInterval(interval: FiniteDuration): MySQLConfig = copy(poolStateLogInterval = interval) override def setPoolName(name: String): MySQLConfig = copy(poolName = name) - override def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig = copy(maxAllowedPacket = maxAllowedPacket) + override def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig = { + require(maxAllowedPacket >= MIN_PACKET_SIZE, s"maxAllowedPacket must be at least $MIN_PACKET_SIZE bytes, but got $maxAllowedPacket") + require(maxAllowedPacket <= MAX_PACKET_SIZE, s"maxAllowedPacket must not exceed $MAX_PACKET_SIZE bytes (MySQL protocol limit), but got $maxAllowedPacket") + copy(maxAllowedPacket = maxAllowedPacket) + } /** * Creates a default MySQLConfig with standard connection parameters. From 75f9549682f489273a7e1dc2db1dd00ccafde18a Mon Sep 17 00:00:00 2001 From: takapi327 Date: Thu, 25 Dec 2025 19:28:41 +0900 Subject: [PATCH 6/9] Added max packet size range check for datasource --- .../main/scala/ldbc/connector/MySQLDataSource.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala index 61d70572c..684887225 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala @@ -85,7 +85,7 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen tracer: Option[Tracer[F]] = None, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = MySQLConfig.DEFAULT_PACKET_SIZE, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]], before: Option[Connection[F] => F[A]] = None, @@ -263,11 +263,15 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen /** Sets the maximum allowed packet size for network communication. * - * @param maxAllowedPacket the maximum packet size in bytes (0 to 16,777,215) + * @param maxAllowedPacket the maximum packet size in bytes (1,024 to 16,777,215) * @return a new MySQLDataSource with the updated packet size limit + * @throws IllegalArgumentException if the value is outside the valid range */ - def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLDataSource[F, A] = + def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLDataSource[F, A] = { + require(maxAllowedPacket >= MySQLConfig.MIN_PACKET_SIZE, s"maxAllowedPacket must be at least ${MySQLConfig.MIN_PACKET_SIZE} bytes, but got $maxAllowedPacket") + require(maxAllowedPacket <= MySQLConfig.MAX_PACKET_SIZE, s"maxAllowedPacket must not exceed ${MySQLConfig.MAX_PACKET_SIZE} bytes (MySQL protocol limit), but got $maxAllowedPacket") copy(maxAllowedPacket = maxAllowedPacket) + } /** Sets whether to authentication plugin to be used first for communication with the server. * @param defaultAuthenticationPlugin From c9e3ec93418887c60c2b28c683efacee6f47266a Mon Sep 17 00:00:00 2001 From: takapi327 Date: Thu, 25 Dec 2025 19:29:09 +0900 Subject: [PATCH 7/9] Change use default max packet size --- .../src/main/scala/ldbc/connector/Connection.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala index ea81e4a10..eb355d5a7 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/Connection.scala @@ -77,7 +77,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = MySQLConfig.DEFAULT_PACKET_SIZE, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]] @@ -117,7 +117,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = MySQLConfig.DEFAULT_PACKET_SIZE, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]] @@ -155,7 +155,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = MySQLConfig.DEFAULT_PACKET_SIZE, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = Some(DatabaseMetaData.DatabaseTerm.CATALOG), defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]] = None, plugins: List[AuthenticationPlugin[F]] = List.empty[AuthenticationPlugin[F]], @@ -203,7 +203,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = MySQLConfig.DEFAULT_PACKET_SIZE, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = None, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: List[AuthenticationPlugin[F]], @@ -268,7 +268,7 @@ object Connection: allowPublicKeyRetrieval: Boolean = false, useCursorFetch: Boolean = false, useServerPrepStmts: Boolean = false, - maxAllowedPacket: Int = 65535, + maxAllowedPacket: Int = MySQLConfig.DEFAULT_PACKET_SIZE, databaseTerm: Option[DatabaseMetaData.DatabaseTerm] = None, defaultAuthenticationPlugin: Option[AuthenticationPlugin[F]], plugins: List[AuthenticationPlugin[F]], From 7e4d4127a68b16919b160a222ed4cd5d5f0cb97f Mon Sep 17 00:00:00 2001 From: takapi327 Date: Thu, 25 Dec 2025 19:39:51 +0900 Subject: [PATCH 8/9] Added max packet size test --- .../ldbc/connector/MySQLConfigTest.scala | 65 +++++++++++++++++++ .../ldbc/connector/MySQLDataSourceTest.scala | 61 +++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala index 7717485a4..acf6aafdb 100644 --- a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala +++ b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala @@ -30,6 +30,7 @@ class MySQLConfigTest extends FTestPlatform: assertEquals(config.databaseTerm, Some(DatabaseMetaData.DatabaseTerm.CATALOG)) assertEquals(config.useCursorFetch, false) assertEquals(config.useServerPrepStmts, false) + assertEquals(config.maxAllowedPacket, MySQLConfig.DEFAULT_PACKET_SIZE) } test("setHost should update host value") { @@ -130,6 +131,68 @@ class MySQLConfigTest extends FTestPlatform: assertEquals(updated.useServerPrepStmts, true) } + test("setMaxAllowedPacket should update maxAllowedPacket value") { + val config = MySQLConfig.default + val updated = config.setMaxAllowedPacket(1048576) // 1MB + + assertEquals(updated.maxAllowedPacket, 1048576) + // Ensure other values remain unchanged + assertEquals(updated.host, config.host) + assertEquals(updated.port, config.port) + } + + test("setMaxAllowedPacket should accept minimum valid value") { + val config = MySQLConfig.default + val updated = config.setMaxAllowedPacket(MySQLConfig.MIN_PACKET_SIZE) + + assertEquals(updated.maxAllowedPacket, MySQLConfig.MIN_PACKET_SIZE) + } + + test("setMaxAllowedPacket should accept maximum valid value") { + val config = MySQLConfig.default + val updated = config.setMaxAllowedPacket(MySQLConfig.MAX_PACKET_SIZE) + + assertEquals(updated.maxAllowedPacket, MySQLConfig.MAX_PACKET_SIZE) + } + + test("setMaxAllowedPacket should reject values below minimum") { + val config = MySQLConfig.default + + intercept[IllegalArgumentException] { + config.setMaxAllowedPacket(MySQLConfig.MIN_PACKET_SIZE - 1) + } + } + + test("setMaxAllowedPacket should reject values above maximum") { + val config = MySQLConfig.default + + intercept[IllegalArgumentException] { + config.setMaxAllowedPacket(MySQLConfig.MAX_PACKET_SIZE + 1) + } + } + + test("setMaxAllowedPacket should reject zero value") { + val config = MySQLConfig.default + + intercept[IllegalArgumentException] { + config.setMaxAllowedPacket(0) + } + } + + test("setMaxAllowedPacket should reject negative values") { + val config = MySQLConfig.default + + intercept[IllegalArgumentException] { + config.setMaxAllowedPacket(-1) + } + } + + test("MySQLConfig constants should have expected values") { + assertEquals(MySQLConfig.MIN_PACKET_SIZE, 1024) + assertEquals(MySQLConfig.MAX_PACKET_SIZE, 16777215) + assertEquals(MySQLConfig.DEFAULT_PACKET_SIZE, 65535) + } + test("MySQLConfig should be immutable - original config should not change") { val original = MySQLConfig.default val originalHost = original.host @@ -167,6 +230,7 @@ class MySQLConfigTest extends FTestPlatform: .setAllowPublicKeyRetrieval(true) .setUseCursorFetch(true) .setUseServerPrepStmts(true) + .setMaxAllowedPacket(1048576) assertEquals(config.host, "localhost") assertEquals(config.port, 3307) @@ -178,6 +242,7 @@ class MySQLConfigTest extends FTestPlatform: assertEquals(config.allowPublicKeyRetrieval, true) assertEquals(config.useCursorFetch, true) assertEquals(config.useServerPrepStmts, true) + assertEquals(config.maxAllowedPacket, 1048576) } test("MySQLConfig with custom socket options") { diff --git a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala index e343c0366..c729cfdfa 100644 --- a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala +++ b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala @@ -40,6 +40,7 @@ class MySQLDataSourceTest extends FTestPlatform: assertEquals(dataSource.databaseTerm, Some(DatabaseMetaData.DatabaseTerm.CATALOG)) assertEquals(dataSource.useCursorFetch, false) assertEquals(dataSource.useServerPrepStmts, false) + assertEquals(dataSource.maxAllowedPacket, MySQLConfig.DEFAULT_PACKET_SIZE) assertEquals(dataSource.before, None) assertEquals(dataSource.after, None) } @@ -149,6 +150,62 @@ class MySQLDataSourceTest extends FTestPlatform: assertEquals(updated.useServerPrepStmts, true) } + test("setMaxAllowedPacket should update maxAllowedPacket value") { + val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") + val updated = dataSource.setMaxAllowedPacket(1048576) // 1MB + + assertEquals(updated.maxAllowedPacket, 1048576) + // Ensure other values remain unchanged + assertEquals(updated.host, dataSource.host) + assertEquals(updated.port, dataSource.port) + } + + test("setMaxAllowedPacket should accept minimum valid value") { + val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") + val updated = dataSource.setMaxAllowedPacket(MySQLConfig.MIN_PACKET_SIZE) + + assertEquals(updated.maxAllowedPacket, MySQLConfig.MIN_PACKET_SIZE) + } + + test("setMaxAllowedPacket should accept maximum valid value") { + val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") + val updated = dataSource.setMaxAllowedPacket(MySQLConfig.MAX_PACKET_SIZE) + + assertEquals(updated.maxAllowedPacket, MySQLConfig.MAX_PACKET_SIZE) + } + + test("setMaxAllowedPacket should reject values below minimum") { + val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") + + intercept[IllegalArgumentException] { + dataSource.setMaxAllowedPacket(MySQLConfig.MIN_PACKET_SIZE - 1) + } + } + + test("setMaxAllowedPacket should reject values above maximum") { + val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") + + intercept[IllegalArgumentException] { + dataSource.setMaxAllowedPacket(MySQLConfig.MAX_PACKET_SIZE + 1) + } + } + + test("setMaxAllowedPacket should reject zero value") { + val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") + + intercept[IllegalArgumentException] { + dataSource.setMaxAllowedPacket(0) + } + } + + test("setMaxAllowedPacket should reject negative values") { + val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") + + intercept[IllegalArgumentException] { + dataSource.setMaxAllowedPacket(-1) + } + } + test("withBefore should add a before hook and change type parameter") { val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") val beforeHook: Connection[IO] => IO[String] = _ => IO.pure("before result") @@ -191,6 +248,7 @@ class MySQLDataSourceTest extends FTestPlatform: .setAllowPublicKeyRetrieval(true) .setUseCursorFetch(true) .setUseServerPrepStmts(true) + .setMaxAllowedPacket(1048576) assertEquals(dataSource.host, "127.0.0.1") assertEquals(dataSource.port, 3307) @@ -202,6 +260,7 @@ class MySQLDataSourceTest extends FTestPlatform: assertEquals(dataSource.allowPublicKeyRetrieval, true) assertEquals(dataSource.useCursorFetch, true) assertEquals(dataSource.useServerPrepStmts, true) + assertEquals(dataSource.maxAllowedPacket, 1048576) } test("MySQLDataSource.fromConfig should create DataSource from MySQLConfig") { @@ -212,6 +271,7 @@ class MySQLDataSourceTest extends FTestPlatform: .setPassword("configpass") .setDatabase("configdb") .setDebug(true) + .setMaxAllowedPacket(2097152) // 2MB val dataSource = MySQLDataSource.fromConfig[IO](config) @@ -221,6 +281,7 @@ class MySQLDataSourceTest extends FTestPlatform: assertEquals(dataSource.password, Some("configpass")) assertEquals(dataSource.database, Some("configdb")) assertEquals(dataSource.debug, true) + assertEquals(dataSource.maxAllowedPacket, 2097152) } test("MySQLDataSource.default should create DataSource with default config") { From 9e2689224eb6a3f84af2f0941de466619eef0e9f Mon Sep 17 00:00:00 2001 From: takapi327 Date: Thu, 25 Dec 2025 19:40:43 +0900 Subject: [PATCH 9/9] Action sbt scalafmtAll --- .../main/scala/ldbc/connector/MySQLConfig.scala | 16 +++++++++++----- .../scala/ldbc/connector/MySQLDataSource.scala | 10 ++++++++-- .../scala/ldbc/connector/MySQLConfigTest.scala | 8 ++++---- .../ldbc/connector/MySQLDataSourceTest.scala | 8 ++++---- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala index fd04508e7..486c2dd18 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLConfig.scala @@ -466,10 +466,10 @@ object MySQLConfig: /** Minimum allowed packet size in bytes (1KB) */ val MIN_PACKET_SIZE: Int = 1024 - + /** Maximum allowed packet size in bytes (16MB - MySQL protocol limit) */ val MAX_PACKET_SIZE: Int = 16777215 - + /** Default packet size in bytes (64KB - MySQL JDBC Driver compatible) */ val DEFAULT_PACKET_SIZE: Int = 65535 @@ -544,9 +544,15 @@ object MySQLConfig: override def setLogPoolState(enabled: Boolean): MySQLConfig = copy(logPoolState = enabled) override def setPoolStateLogInterval(interval: FiniteDuration): MySQLConfig = copy(poolStateLogInterval = interval) override def setPoolName(name: String): MySQLConfig = copy(poolName = name) - override def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig = { - require(maxAllowedPacket >= MIN_PACKET_SIZE, s"maxAllowedPacket must be at least $MIN_PACKET_SIZE bytes, but got $maxAllowedPacket") - require(maxAllowedPacket <= MAX_PACKET_SIZE, s"maxAllowedPacket must not exceed $MAX_PACKET_SIZE bytes (MySQL protocol limit), but got $maxAllowedPacket") + override def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLConfig = { + require( + maxAllowedPacket >= MIN_PACKET_SIZE, + s"maxAllowedPacket must be at least $MIN_PACKET_SIZE bytes, but got $maxAllowedPacket" + ) + require( + maxAllowedPacket <= MAX_PACKET_SIZE, + s"maxAllowedPacket must not exceed $MAX_PACKET_SIZE bytes (MySQL protocol limit), but got $maxAllowedPacket" + ) copy(maxAllowedPacket = maxAllowedPacket) } diff --git a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala index 684887225..22599d55c 100644 --- a/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala +++ b/module/ldbc-connector/shared/src/main/scala/ldbc/connector/MySQLDataSource.scala @@ -268,8 +268,14 @@ final case class MySQLDataSource[F[_]: Async: Network: Console: Hashing: UUIDGen * @throws IllegalArgumentException if the value is outside the valid range */ def setMaxAllowedPacket(maxAllowedPacket: Int): MySQLDataSource[F, A] = { - require(maxAllowedPacket >= MySQLConfig.MIN_PACKET_SIZE, s"maxAllowedPacket must be at least ${MySQLConfig.MIN_PACKET_SIZE} bytes, but got $maxAllowedPacket") - require(maxAllowedPacket <= MySQLConfig.MAX_PACKET_SIZE, s"maxAllowedPacket must not exceed ${MySQLConfig.MAX_PACKET_SIZE} bytes (MySQL protocol limit), but got $maxAllowedPacket") + require( + maxAllowedPacket >= MySQLConfig.MIN_PACKET_SIZE, + s"maxAllowedPacket must be at least ${ MySQLConfig.MIN_PACKET_SIZE } bytes, but got $maxAllowedPacket" + ) + require( + maxAllowedPacket <= MySQLConfig.MAX_PACKET_SIZE, + s"maxAllowedPacket must not exceed ${ MySQLConfig.MAX_PACKET_SIZE } bytes (MySQL protocol limit), but got $maxAllowedPacket" + ) copy(maxAllowedPacket = maxAllowedPacket) } diff --git a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala index acf6aafdb..e84f6a958 100644 --- a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala +++ b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLConfigTest.scala @@ -157,7 +157,7 @@ class MySQLConfigTest extends FTestPlatform: test("setMaxAllowedPacket should reject values below minimum") { val config = MySQLConfig.default - + intercept[IllegalArgumentException] { config.setMaxAllowedPacket(MySQLConfig.MIN_PACKET_SIZE - 1) } @@ -165,7 +165,7 @@ class MySQLConfigTest extends FTestPlatform: test("setMaxAllowedPacket should reject values above maximum") { val config = MySQLConfig.default - + intercept[IllegalArgumentException] { config.setMaxAllowedPacket(MySQLConfig.MAX_PACKET_SIZE + 1) } @@ -173,7 +173,7 @@ class MySQLConfigTest extends FTestPlatform: test("setMaxAllowedPacket should reject zero value") { val config = MySQLConfig.default - + intercept[IllegalArgumentException] { config.setMaxAllowedPacket(0) } @@ -181,7 +181,7 @@ class MySQLConfigTest extends FTestPlatform: test("setMaxAllowedPacket should reject negative values") { val config = MySQLConfig.default - + intercept[IllegalArgumentException] { config.setMaxAllowedPacket(-1) } diff --git a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala index c729cfdfa..e5f63e101 100644 --- a/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala +++ b/module/ldbc-connector/shared/src/test/scala/ldbc/connector/MySQLDataSourceTest.scala @@ -176,7 +176,7 @@ class MySQLDataSourceTest extends FTestPlatform: test("setMaxAllowedPacket should reject values below minimum") { val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") - + intercept[IllegalArgumentException] { dataSource.setMaxAllowedPacket(MySQLConfig.MIN_PACKET_SIZE - 1) } @@ -184,7 +184,7 @@ class MySQLDataSourceTest extends FTestPlatform: test("setMaxAllowedPacket should reject values above maximum") { val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") - + intercept[IllegalArgumentException] { dataSource.setMaxAllowedPacket(MySQLConfig.MAX_PACKET_SIZE + 1) } @@ -192,7 +192,7 @@ class MySQLDataSourceTest extends FTestPlatform: test("setMaxAllowedPacket should reject zero value") { val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") - + intercept[IllegalArgumentException] { dataSource.setMaxAllowedPacket(0) } @@ -200,7 +200,7 @@ class MySQLDataSourceTest extends FTestPlatform: test("setMaxAllowedPacket should reject negative values") { val dataSource = MySQLDataSource[IO, Unit]("localhost", 3306, "root") - + intercept[IllegalArgumentException] { dataSource.setMaxAllowedPacket(-1) }