From b992f58a96b179af27db35ab57e74ec46443be42 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Sat, 6 Apr 2024 13:11:57 +0000 Subject: [PATCH 01/42] [maven-release-plugin] prepare for next development iteration --- r2dbc-mysql/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 7c23bd139..0c26c0a49 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.1.3 + 1.1.4-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - r2dbc-mysql-1.1.3 + HEAD From bcd95f809b9cbee3f5261dfdfeaf641ba692c428 Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 25 Jul 2024 10:42:11 +0900 Subject: [PATCH 02/42] Add CodeQL GitHub Action (#280) Motivation: Integrate CodeQL for automated code analysis to enhance security and code quality. Modifications: Add GitHub Action Result: Maintain high standards of code security and quality in the project. --- .github/workflows/ci-codeql.yml | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/ci-codeql.yml diff --git a/.github/workflows/ci-codeql.yml b/.github/workflows/ci-codeql.yml new file mode 100644 index 000000000..cc2347469 --- /dev/null +++ b/.github/workflows/ci-codeql.yml @@ -0,0 +1,80 @@ +# Copyright 2024 asyncer.io proejcts +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "CodeQL" + +on: + push: + branches: [ "trunk", "0.9.x" ] + pull_request: + branches: [ "trunk", "0.9.x" ] + schedule: + - cron: '24 3 * * 5' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: java-kotlin + build-mode: none # This mode only analyzes Java. Set this to 'autobuild' or 'manual' to analyze Kotlin too. + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From 508d6c38d9354951e3621b963114a1f3d7a75359 Mon Sep 17 00:00:00 2001 From: ZhangJian He Date: Thu, 25 Jul 2024 10:30:37 +0800 Subject: [PATCH 03/42] chore: fix typo in ReactorNettyClient (#278) --- .../java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java index 81cb5f21e..961b2806e 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java @@ -118,7 +118,7 @@ final class ReactorNettyClient implements Client { } sink.next((ServerMessage) it); } else { - // ReferenceCounted will released by Netty. + // ReferenceCounted will be released by Netty. throw ClientExceptions.unsupportedProtocol(it.getClass().getTypeName()); } }) From e37cbddc2fdfb9ad2b12ea27425502a06c03b448 Mon Sep 17 00:00:00 2001 From: ZhangJian He Date: Thu, 25 Jul 2024 19:45:29 +0800 Subject: [PATCH 04/42] feat: support config AddressResolverGroup in r2dbc-mysql (#279) Motivation: Currently,`AddressResolverGroup` can't be configured. The DnsResolver default start address listen to "0.0.0.0", which may have some security risks. also see https://github.com/netty/netty/pull/11061 Modification: Add `AddressResolverGroup` in Client's connect method --------- Signed-off-by: ZhangJian He --- .../mysql/MySqlConnectionConfiguration.java | 83 ++++++++++++------- .../r2dbc/mysql/MySqlConnectionFactory.java | 3 +- .../mysql/MySqlConnectionFactoryProvider.java | 14 ++++ .../io/asyncer/r2dbc/mysql/client/Client.java | 7 +- .../MySqlConnectionConfigurationTest.java | 15 ++++ .../MySqlConnectionFactoryProviderTest.java | 16 ++++ 6 files changed, 104 insertions(+), 34 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java index 3856b58bd..2f1c75961 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java @@ -22,6 +22,7 @@ import io.asyncer.r2dbc.mysql.extension.Extension; import io.asyncer.r2dbc.mysql.internal.util.InternalArrays; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.resolver.AddressResolverGroup; import org.jetbrains.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.netty.resources.LoopResources; @@ -127,6 +128,9 @@ public final class MySqlConnectionConfiguration { @Nullable private final Publisher passwordPublisher; + @Nullable + private final AddressResolverGroup resolver; + private MySqlConnectionConfiguration( boolean isHost, String domain, int port, MySqlSslConfiguration ssl, boolean tcpKeepAlive, boolean tcpNoDelay, @Nullable Duration connectTimeout, @@ -141,7 +145,8 @@ private MySqlConnectionConfiguration( int queryCacheSize, int prepareCacheSize, Set compressionAlgorithms, int zstdCompressionLevel, @Nullable LoopResources loopResources, - Extensions extensions, @Nullable Publisher passwordPublisher + Extensions extensions, @Nullable Publisher passwordPublisher, + @Nullable AddressResolverGroup resolver ) { this.isHost = isHost; this.domain = domain; @@ -171,6 +176,7 @@ private MySqlConnectionConfiguration( this.loopResources = loopResources == null ? TcpResources.get() : loopResources; this.extensions = extensions; this.passwordPublisher = passwordPublisher; + this.resolver = resolver; } /** @@ -301,6 +307,11 @@ Publisher getPasswordPublisher() { return passwordPublisher; } + @Nullable + AddressResolverGroup getResolver() { + return resolver; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -337,7 +348,8 @@ public boolean equals(Object o) { zstdCompressionLevel == that.zstdCompressionLevel && Objects.equals(loopResources, that.loopResources) && extensions.equals(that.extensions) && - Objects.equals(passwordPublisher, that.passwordPublisher); + Objects.equals(passwordPublisher, that.passwordPublisher) && + Objects.equals(resolver, that.resolver); } @Override @@ -352,19 +364,26 @@ public int hashCode() { loadLocalInfilePath, localInfileBufferSize, queryCacheSize, prepareCacheSize, compressionAlgorithms, zstdCompressionLevel, - loopResources, extensions, passwordPublisher); + loopResources, extensions, passwordPublisher, resolver); } @Override public String toString() { - if (isHost) { - return "MySqlConnectionConfiguration{host='" + domain + "', port=" + port + ", ssl=" + ssl + - ", tcpNoDelay=" + tcpNoDelay + ", tcpKeepAlive=" + tcpKeepAlive + - ", connectTimeout=" + connectTimeout + + return "MySqlConnectionConfiguration{" + + (isHost ? "host='" + domain + "', port=" + port + ", ssl=" + ssl + + ", tcpNoDelay=" + tcpNoDelay + ", tcpKeepAlive=" + tcpKeepAlive : + "unixSocket='" + domain + "'") + + buildCommonToStringPart() + + '}'; + } + + private String buildCommonToStringPart() { + return ", connectTimeout=" + connectTimeout + ", preserveInstants=" + preserveInstants + ", connectionTimeZone=" + connectionTimeZone + ", forceConnectionTimeZoneToSession=" + forceConnectionTimeZoneToSession + - ", zeroDateOption=" + zeroDateOption + ", user='" + user + "', password=" + password + + ", zeroDateOption=" + zeroDateOption + + ", user='" + user + "', password=" + password + ", database='" + database + "', createDatabaseIfNotExist=" + createDatabaseIfNotExist + ", preferPrepareStatement=" + preferPrepareStatement + ", sessionVariables=" + sessionVariables + @@ -372,32 +391,14 @@ public String toString() { ", statementTimeout=" + statementTimeout + ", loadLocalInfilePath=" + loadLocalInfilePath + ", localInfileBufferSize=" + localInfileBufferSize + - ", queryCacheSize=" + queryCacheSize + ", prepareCacheSize=" + prepareCacheSize + + ", queryCacheSize=" + queryCacheSize + + ", prepareCacheSize=" + prepareCacheSize + ", compressionAlgorithms=" + compressionAlgorithms + ", zstdCompressionLevel=" + zstdCompressionLevel + ", loopResources=" + loopResources + - ", extensions=" + extensions + ", passwordPublisher=" + passwordPublisher + '}'; - } - - return "MySqlConnectionConfiguration{unixSocket='" + domain + - "', connectTimeout=" + connectTimeout + - ", preserveInstants=" + preserveInstants + - ", connectionTimeZone=" + connectionTimeZone + - ", forceConnectionTimeZoneToSession=" + forceConnectionTimeZoneToSession + - ", zeroDateOption=" + zeroDateOption + ", user='" + user + "', password=" + password + - ", database='" + database + "', createDatabaseIfNotExist=" + createDatabaseIfNotExist + - ", preferPrepareStatement=" + preferPrepareStatement + - ", sessionVariables=" + sessionVariables + - ", lockWaitTimeout=" + lockWaitTimeout + - ", statementTimeout=" + statementTimeout + - ", loadLocalInfilePath=" + loadLocalInfilePath + - ", localInfileBufferSize=" + localInfileBufferSize + - ", queryCacheSize=" + queryCacheSize + - ", prepareCacheSize=" + prepareCacheSize + - ", compressionAlgorithms=" + compressionAlgorithms + - ", zstdCompressionLevel=" + zstdCompressionLevel + - ", loopResources=" + loopResources + - ", extensions=" + extensions + ", passwordPublisher=" + passwordPublisher + '}'; + ", extensions=" + extensions + + ", passwordPublisher=" + passwordPublisher + + ", resolver=" + resolver; } /** @@ -494,6 +495,9 @@ public static final class Builder { @Nullable private Publisher passwordPublisher; + @Nullable + private AddressResolverGroup resolver; + /** * Builds an immutable {@link MySqlConnectionConfiguration} with current options. * @@ -528,7 +532,7 @@ public MySqlConnectionConfiguration build() { loadLocalInfilePath, localInfileBufferSize, queryCacheSize, prepareCacheSize, compressionAlgorithms, zstdCompressionLevel, loopResources, - Extensions.from(extensions, autodetectExtensions), passwordPublisher); + Extensions.from(extensions, autodetectExtensions), passwordPublisher, resolver); } /** @@ -1156,6 +1160,21 @@ public Builder passwordPublisher(Publisher passwordPublisher) { return this; } + /** + * Sets the {@link AddressResolverGroup} for resolving host addresses. + *

+ * This can be used to customize the DNS resolution mechanism, which is particularly useful in environments + * with specific DNS configuration needs or where a custom DNS resolver is required. + * + * @param resolver the resolver group to use for host address resolution. + * @return this {@link Builder}. + * @since 1.2.0 + */ + public Builder resolver(AddressResolverGroup resolver) { + this.resolver = resolver; + return this; + } + private SslMode requireSslMode() { SslMode sslMode = this.sslMode; diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java index d003db2b0..bff85c809 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java @@ -147,7 +147,8 @@ private static Mono getMySqlConnection( configuration.isTcpNoDelay(), context, configuration.getConnectTimeout(), - configuration.getLoopResources() + configuration.getLoopResources(), + configuration.getResolver() )).flatMap(client -> { // Lazy init database after handshake/login boolean deferDatabase = configuration.isCreateDatabaseIfNotExist(); diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java index f6dc1a57a..d89005394 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java @@ -20,6 +20,7 @@ import io.asyncer.r2dbc.mysql.constant.SslMode; import io.asyncer.r2dbc.mysql.constant.ZeroDateOption; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.resolver.AddressResolverGroup; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import io.r2dbc.spi.ConnectionFactoryProvider; @@ -308,6 +309,17 @@ public final class MySqlConnectionFactoryProvider implements ConnectionFactoryPr */ public static final Option> PASSWORD_PUBLISHER = Option.valueOf("passwordPublisher"); + /** + * Option to set the {@link AddressResolverGroup} for resolving host addresses. + *

+ * This can be used to customize the DNS resolution mechanism, which is particularly useful in environments + * with specific DNS configuration needs or where a custom DNS resolver is required. + *

+ * + * @since 1.2.0 + */ + public static final Option> RESOLVER = Option.valueOf("resolver"); + @Override public ConnectionFactory create(ConnectionFactoryOptions options) { requireNonNull(options, "connectionFactoryOptions must not be null"); @@ -389,6 +401,8 @@ static MySqlConnectionConfiguration setup(ConnectionFactoryOptions options) { .to(builder::loopResources); mapper.optional(PASSWORD_PUBLISHER).as(Publisher.class) .to(builder::passwordPublisher); + mapper.optional(RESOLVER).as(AddressResolverGroup.class) + .to(builder::resolver); mapper.optional(SESSION_VARIABLES).asArray( String[].class, Function.identity(), diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java index d7c3ac28a..0beaf4c0d 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java @@ -22,6 +22,7 @@ import io.asyncer.r2dbc.mysql.message.server.ServerMessage; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelOption; +import io.netty.resolver.AddressResolverGroup; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import org.jetbrains.annotations.Nullable; @@ -132,7 +133,7 @@ public interface Client { */ static Mono connect(MySqlSslConfiguration ssl, SocketAddress address, boolean tcpKeepAlive, boolean tcpNoDelay, ConnectionContext context, @Nullable Duration connectTimeout, - LoopResources loopResources) { + LoopResources loopResources, @Nullable AddressResolverGroup resolver) { requireNonNull(ssl, "ssl must not be null"); requireNonNull(address, "address must not be null"); requireNonNull(context, "context must not be null"); @@ -150,6 +151,10 @@ static Mono connect(MySqlSslConfiguration ssl, SocketAddress address, bo tcpClient = tcpClient.option(ChannelOption.TCP_NODELAY, tcpNoDelay); } + if (resolver != null) { + tcpClient = tcpClient.resolver(resolver); + } + return tcpClient.remoteAddress(() -> address).connect() .map(conn -> new ReactorNettyClient(conn, ssl, context)); } diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java index f050f4e4a..f05defb17 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java @@ -22,6 +22,8 @@ import io.asyncer.r2dbc.mysql.constant.ZeroDateOption; import io.asyncer.r2dbc.mysql.extension.Extension; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.resolver.AddressResolverGroup; +import io.netty.resolver.DefaultAddressResolverGroup; import org.assertj.core.api.ObjectAssert; import org.assertj.core.api.ThrowableTypeAssert; import org.jetbrains.annotations.Nullable; @@ -207,6 +209,19 @@ void validPasswordSupplier() { .verifyComplete(); } + @Test + void validResolver() { + final AddressResolverGroup resolver = DefaultAddressResolverGroup.INSTANCE; + AddressResolverGroup resolverGroup = MySqlConnectionConfiguration.builder() + .host(HOST) + .user(USER) + .resolver(resolver) + .autodetectExtensions(false) + .build() + .getResolver(); + assertThat(resolverGroup).isSameAs(resolver); + } + private static MySqlConnectionConfiguration unixSocketSslMode(SslMode sslMode) { return MySqlConnectionConfiguration.builder() .unixSocket(UNIX_SOCKET) diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java index ab75161c1..1e71a9f17 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java @@ -20,6 +20,8 @@ import io.asyncer.r2dbc.mysql.constant.SslMode; import io.asyncer.r2dbc.mysql.constant.ZeroDateOption; import io.netty.handler.ssl.SslContextBuilder; +import io.netty.resolver.AddressResolverGroup; +import io.netty.resolver.DefaultAddressResolverGroup; import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactoryOptions; import io.r2dbc.spi.Option; @@ -50,6 +52,7 @@ import java.util.stream.Stream; import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.PASSWORD_PUBLISHER; +import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.RESOLVER; import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.USE_SERVER_PREPARE_STATEMENT; import static io.r2dbc.spi.ConnectionFactoryOptions.CONNECT_TIMEOUT; import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE; @@ -453,6 +456,19 @@ void validPasswordSupplier() { assertThat(ConnectionFactories.get(options)).isExactlyInstanceOf(MySqlConnectionFactory.class); } + @Test + void validResolver() { + final AddressResolverGroup resolver = DefaultAddressResolverGroup.INSTANCE; + ConnectionFactoryOptions options = ConnectionFactoryOptions.builder() + .option(DRIVER, "mysql") + .option(HOST, "127.0.0.1") + .option(USER, "root") + .option(RESOLVER, resolver) + .build(); + + assertThat(ConnectionFactories.get(options)).isExactlyInstanceOf(MySqlConnectionFactory.class); + } + @Test void allConfigurationOptions() { List exceptConfigs = Arrays.asList( From a5ada25ccf1e16d758606f7cba7a47a4e70a5562 Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 25 Jul 2024 21:01:27 +0900 Subject: [PATCH 05/42] Prepare Next Release (#281) Motivation: Will Introduce new feature. Modification: Set Version 1.2.0 Result: Version 1.2.0 --- r2dbc-mysql/pom.xml | 2 +- test-native-image/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 0c26c0a49..7d7785132 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.1.4-SNAPSHOT + 1.2.0-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index e18ad93bb..abb1f56fd 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.1.4-SNAPSHOT + 1.2.0-SNAPSHOT UTF-8 From 17fe67b0471dbc20f34178d48b5493aba2bf9cc8 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 28 Jul 2024 23:19:31 +0900 Subject: [PATCH 06/42] Support MySQL 8.4 and 9.0 (#283) Motivation: Add compatibility for the newer MySQL versions 8.4 and 9.0 to ensure the connector works with the latest MySQL features and improvements. Modifications: - Added tests to verify functionality with MySQL 8.4 and 9.0. Result: Ensures compatibility with MySQL 8.4 and 9.0. --- .github/workflows/ci-integration-tests.yml | 2 +- README.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-integration-tests.yml b/.github/workflows/ci-integration-tests.yml index 6f0e151ff..8cf8991d2 100644 --- a/.github/workflows/ci-integration-tests.yml +++ b/.github/workflows/ci-integration-tests.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - mysql-version: [ 5.5, 5.6.45, 5.6, 5.7.28, 5.7, 8.0, 8.1, 8.2, 8.3] + mysql-version: [ 5.5, 5.6.45, 5.6, 5.7.28, 5.7, 8.0, 8.1, 8.2, 8.3, 8.4, 9.0] name: Integration test with MySQL ${{ matrix.mysql-version }} steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 4ddab2f62..ba852f8d0 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,9 @@ This driver provides the following features: ![MySQL 8.0 status](https://img.shields.io/badge/MySQL%208.0-pass-blue) ![MySQL 8.1 status](https://img.shields.io/badge/MySQL%208.1-pass-blue) ![MySQL 8.2 status](https://img.shields.io/badge/MySQL%208.2-pass-blue) +![MySQL 8.3 status](https://img.shields.io/badge/MySQL%208.3-pass-blue) +![MySQL 8.4 status](https://img.shields.io/badge/MySQL%208.4-pass-blue) +![MySQL 9.0 status](https://img.shields.io/badge/MySQL%209.0-pass-blue) ![MariaDB 10.6 status](https://img.shields.io/badge/MariaDB%2010.6-pass-blue) ![MariaDB 10.11 status](https://img.shields.io/badge/MariaDB%2010.11-pass-blue) From ef9ee1d0e194f45996c138eaa08e1c618da9cdd7 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Mon, 29 Jul 2024 11:18:48 +0000 Subject: [PATCH 07/42] [maven-release-plugin] prepare release r2dbc-mysql-1.2.0 --- r2dbc-mysql/pom.xml | 4 ++-- test-native-image/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 7d7785132..5576bf390 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.2.0-SNAPSHOT + 1.2.0 Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - HEAD + r2dbc-mysql-1.2.0 diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index abb1f56fd..0f4eecbcb 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.2.0-SNAPSHOT + 1.2.1-SNAPSHOT UTF-8 From 102c99a33a989f88bb502900ccfeed01d9646f53 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Mon, 29 Jul 2024 11:18:50 +0000 Subject: [PATCH 08/42] [maven-release-plugin] prepare for next development iteration --- r2dbc-mysql/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 5576bf390..f066f06d3 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.2.0 + 1.2.1-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - r2dbc-mysql-1.2.0 + HEAD From bc227f176b868b4ee51c8a2ab2412c6252b1e4ed Mon Sep 17 00:00:00 2001 From: jchrys Date: Sat, 3 Aug 2024 22:07:19 +0900 Subject: [PATCH 09/42] Prepare Next Release (#284) Motivation: Will introduce breaking change. Modification: Set Version 1.3.0 Result: Version 1.3.0 --- README.md | 8 ++++---- r2dbc-mysql/pom.xml | 2 +- test-native-image/pom.xml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ba852f8d0..e581a7159 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Refer to the table below to determine the appropriate version of r2dbc-mysql for | spring-boot-starter-data-r2dbc | spring-data-r2dbc | r2dbc-spi | r2dbc-mysql(recommended) | |--------------------------------|-------------------|---------------|------------------------------| -| 3.0.* and above | 3.0.* and above | 1.0.0.RELEASE | io.asyncer:r2dbc-mysql:1.1.0 | +| 3.0.* and above | 3.0.* and above | 1.0.0.RELEASE | io.asyncer:r2dbc-mysql:1.2.0 | | 2.7.* | 1.5.* | 0.9.1.RELEASE | io.asyncer:r2dbc-mysql:0.9.7 | | 2.6.* and below | 1.4.* and below | 0.8.6.RELEASE | dev.miku:r2dbc-mysql:0.8.2 | @@ -61,7 +61,7 @@ However, Docker-certified images do not have these versions lower than 5.5.0, so io.asyncer r2dbc-mysql - 1.1.0 + 1.2.0 ``` @@ -71,7 +71,7 @@ However, Docker-certified images do not have these versions lower than 5.5.0, so ```groovy dependencies { - implementation 'io.asyncer:r2dbc-mysql:1.1.0' + implementation 'io.asyncer:r2dbc-mysql:1.2.0' } ``` @@ -80,7 +80,7 @@ dependencies { ```kotlin dependencies { // Maybe should to use `compile` instead of `implementation` on the lower version of Gradle. - implementation("io.asyncer:r2dbc-mysql:1.1.0") + implementation("io.asyncer:r2dbc-mysql:1.2.0") } ``` diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index f066f06d3..29d0563b3 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.2.1-SNAPSHOT + 1.3.0-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index 0f4eecbcb..ce5cacece 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.2.1-SNAPSHOT + 1.3.0-SNAPSHOT UTF-8 From f56eaa86918879dd347ffdda6094c9cd3d94b420 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 4 Aug 2024 11:41:56 +0900 Subject: [PATCH 10/42] Handle `BIT(1)` as `Boolean` by Default (#286) Motivation: BIT(1) should be treated as `Boolean`. Modification: set `Boolean.class` as `BIT(1)'s default java type. Result: Ensures `BIT(1)` is handled as `Boolean` by default. like mysql-connector-j's implementation. Resolves #277 --- .../asyncer/r2dbc/mysql/codec/DefaultCodecs.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java index d76b398e2..4542a7c9d 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java @@ -18,6 +18,7 @@ import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; +import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.InternalArrays; import io.asyncer.r2dbc.mysql.message.FieldValue; import io.asyncer.r2dbc.mysql.message.LargeFieldValue; @@ -358,11 +359,21 @@ private T decodeMassive(LargeFieldValue value, MySqlReadableMetadata metadat * @param type the {@link Class} specified by the user. * @return the {@link Class} to use for decoding. */ - private static Class chooseClass(MySqlReadableMetadata metadata, Class type) { - Class javaType = metadata.getType().getJavaType(); + private static Class chooseClass(final MySqlReadableMetadata metadata, Class type) { + final Class javaType = getDefaultJavaType(metadata); return type.isAssignableFrom(javaType) ? javaType : type; } + private static Class getDefaultJavaType(final MySqlReadableMetadata metadata) { + final MySqlType type = metadata.getType(); + // ref: https://github.com/asyncer-io/r2dbc-mysql/issues/277 + // BIT(1) should be treated as Boolean by default. + if (type == MySqlType.BIT && Integer.valueOf(1).equals(metadata.getPrecision())) { + return Boolean.class; + } + return type.getJavaType(); + } + static final class Builder implements CodecsBuilder { @GuardedBy("lock") From e3bd49bfd79a430c08833f5b136934f15c9699b0 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Mon, 5 Aug 2024 12:47:27 +0000 Subject: [PATCH 11/42] [maven-release-plugin] prepare release r2dbc-mysql-1.3.0 --- r2dbc-mysql/pom.xml | 4 ++-- test-native-image/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 29d0563b3..4a11d3e89 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.3.0-SNAPSHOT + 1.3.0 Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - HEAD + r2dbc-mysql-1.3.0 diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index ce5cacece..36ccf08e3 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.3.0-SNAPSHOT + 1.3.1-SNAPSHOT UTF-8 From 037f869878c2acd782021aec8c5b895503fa1463 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Mon, 5 Aug 2024 12:47:29 +0000 Subject: [PATCH 12/42] [maven-release-plugin] prepare for next development iteration --- r2dbc-mysql/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 4a11d3e89..64d20b48b 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.3.0 + 1.3.1-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - r2dbc-mysql-1.3.0 + HEAD From acff42903b2a54a8d526f4ec2c7fc38c397fc567 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 20:39:33 +0900 Subject: [PATCH 13/42] Bump actions/download-artifact from 3 to 4.1.7 (#287) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7. --- .github/workflows/cd-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd-release.yml b/.github/workflows/cd-release.yml index a1fb8bde9..74846be7a 100644 --- a/.github/workflows/cd-release.yml +++ b/.github/workflows/cd-release.yml @@ -77,7 +77,7 @@ jobs: needs: prepare steps: - name: Download workspace - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.7 with: name: prepare-workspace path: ./prepare-workspace/ From 673aac858c864b043d0da3a5a840887c32f7b4f6 Mon Sep 17 00:00:00 2001 From: svats0001 <104880778+svats0001@users.noreply.github.com> Date: Sat, 30 Nov 2024 03:27:54 +1100 Subject: [PATCH 14/42] Implemented relaxed conversion logic for BooleanCodec (#290) @jchrys #285 Motivation: To allow boolean values stored as strings in MySQL database such as "true", "false", "1" and "0" to be converted to their corresponding boolean values. Modification: BooleanCodec: Changed decode method to check if VARCHAR value is a boolean value. Changed doCanDecode to add VARCHAR. BooleanCodecTest: Added decodeString test to ensure boolean values stored as strings are converted into the correct corresponding boolean values. Result: Boolean values stored as strings can now be converted to their corresponding boolean values. Drawbacks are that there could be a string column containing numeric data with values other than 0 or 1 and the column isn't used for storing boolean values at the same time the codec interprets the 0's and 1's as boolean. Only boolean values "true", "false", "1" and "0" are decoded, other possible types of boolean value strings haven't been included. Also, doCanDecode states that the VARCHAR data type can be decoded but only a small subset of this data type can be decoded and it's not possible to highlight the conditions in the doCanDecode method. --- .../r2dbc/mysql/codec/BooleanCodec.java | 49 +++++++- .../r2dbc/mysql/codec/BooleanCodecTest.java | 115 ++++++++++++++++++ 2 files changed, 161 insertions(+), 3 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java index f546ba751..0a59265c3 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java @@ -16,12 +16,15 @@ package io.asyncer.r2dbc.mysql.codec; +import java.math.BigInteger; + import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.r2dbc.spi.R2dbcNonTransientResourceException; import reactor.core.publisher.Mono; /** @@ -38,7 +41,35 @@ private BooleanCodec() { @Override public Boolean decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { - return binary || metadata.getType() == MySqlType.BIT ? value.readBoolean() : value.readByte() != '0'; + MySqlType dataType = metadata.getType(); + + if (dataType == MySqlType.VARCHAR) { + if (!value.isReadable()) { + return createFromLong(0); + } + + String s = value.toString(metadata.getCharCollation(context).getCharset()); + + if (s.equalsIgnoreCase("Y") || s.equalsIgnoreCase("yes") || + s.equalsIgnoreCase("T") || s.equalsIgnoreCase("true")) { + return createFromLong(1); + } else if (s.equalsIgnoreCase("N") || s.equalsIgnoreCase("no") || + s.equalsIgnoreCase("F") || s.equalsIgnoreCase("false")) { + return createFromLong(0); + } else if (s.matches("-?\\d*\\.\\d*") || s.matches("-?\\d*\\.\\d+[eE]-?\\d+") + || s.matches("-?\\d*[eE]-?\\d+")) { + return createFromDouble(Double.parseDouble(s)); + } else if (s.matches("-?\\d+")) { + if (!CodecUtils.isGreaterThanLongMax(s)) { + return createFromLong(CodecUtils.parseLong(value)); + } + return createFromBigInteger(new BigInteger(s)); + } + throw new R2dbcNonTransientResourceException("The value '" + s + "' of type '" + dataType + + "' cannot be encoded into a Boolean.", "22018"); + } + + return binary || dataType == MySqlType.BIT ? value.readBoolean() : value.readByte() != '0'; } @Override @@ -54,8 +85,20 @@ public MySqlParameter encode(Object value, CodecContext context) { @Override public boolean doCanDecode(MySqlReadableMetadata metadata) { MySqlType type = metadata.getType(); - return (type == MySqlType.BIT || type == MySqlType.TINYINT) && - Integer.valueOf(1).equals(metadata.getPrecision()); + return ((type == MySqlType.BIT || type == MySqlType.TINYINT) && + Integer.valueOf(1).equals(metadata.getPrecision())) || type == MySqlType.VARCHAR; + } + + public Boolean createFromLong(long l) { + return (l == -1 || l > 0); + } + + public Boolean createFromDouble(double d) { + return (d == -1.0d || d > 0); + } + + public Boolean createFromBigInteger(BigInteger b) { + return b.compareTo(BigInteger.valueOf(0)) > 0 || b.compareTo(BigInteger.valueOf(-1)) == 0; } private static final class BooleanMySqlParameter extends AbstractMySqlParameter { diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/BooleanCodecTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/BooleanCodecTest.java index 999111de5..dbfd5c104 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/BooleanCodecTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/BooleanCodecTest.java @@ -16,12 +16,22 @@ package io.asyncer.r2dbc.mysql.codec; +import io.asyncer.r2dbc.mysql.ConnectionContextTest; +import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.r2dbc.spi.R2dbcNonTransientException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.nio.charset.Charset; import java.util.Arrays; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; + /** * Unit tests for {@link BooleanCodec}. */ @@ -55,4 +65,109 @@ public ByteBuf[] binaryParameters(Charset charset) { public ByteBuf sized(ByteBuf value) { return value; } + + @Test + void decodeString() { + Codec codec = getCodec(); + Charset c = ConnectionContextTest.mock().getClientCollation().getCharset(); + byte[] bOne = new byte[]{(byte)1}; + byte[] bZero = new byte[]{(byte)0}; + ByteBuffer bitValOne = ByteBuffer.wrap(bOne); + ByteBuffer bitValZero = ByteBuffer.wrap(bZero); + Decoding d1 = new Decoding(Unpooled.copiedBuffer("true", c), "true", MySqlType.VARCHAR); + Decoding d2 = new Decoding(Unpooled.copiedBuffer("false", c), "false", MySqlType.VARCHAR); + Decoding d3 = new Decoding(Unpooled.copiedBuffer("1", c), "1", MySqlType.VARCHAR); + Decoding d4 = new Decoding(Unpooled.copiedBuffer("0", c), "0", MySqlType.VARCHAR); + Decoding d5 = new Decoding(Unpooled.copiedBuffer("Y", c), "Y", MySqlType.VARCHAR); + Decoding d6 = new Decoding(Unpooled.copiedBuffer("no", c), "no", MySqlType.VARCHAR); + Decoding d7 = new Decoding(Unpooled.copiedBuffer("26.57", c), "26.57", MySqlType.VARCHAR); + Decoding d8 = new Decoding(Unpooled.copiedBuffer("-57", c), "=57", MySqlType.VARCHAR); + Decoding d9 = new Decoding(Unpooled.copiedBuffer("100000", c), "100000", MySqlType.VARCHAR); + Decoding d10 = new Decoding(Unpooled.copiedBuffer("-12345678901234567890", c), + "-12345678901234567890", MySqlType.VARCHAR); + Decoding d11 = new Decoding(Unpooled.copiedBuffer("Banana", c), "Banana", MySqlType.VARCHAR); + Decoding d12 = new Decoding(Unpooled.copiedBuffer(bitValOne), bitValOne, MySqlType.BIT); + Decoding d13 = new Decoding(Unpooled.copiedBuffer(bitValZero), bitValZero, MySqlType.BIT); + Decoding d14 = new Decoding(Unpooled.copyDouble(26.57d), 26.57d, MySqlType.DOUBLE); + Decoding d15 = new Decoding(Unpooled.copiedBuffer(bOne), bOne, MySqlType.TINYINT); + Decoding d16 = new Decoding(Unpooled.copiedBuffer(bZero), bZero, MySqlType.TINYINT); + Decoding d17 = new Decoding(Unpooled.copiedBuffer("1e4", c), "1e4", MySqlType.VARCHAR); + Decoding d18 = new Decoding(Unpooled.copiedBuffer("-1.34e10", c), "-1.34e10", MySqlType.VARCHAR); + Decoding d19 = new Decoding(Unpooled.copiedBuffer("-0", c), "-0", MySqlType.VARCHAR); + + assertThat(codec.decode(d1.content(), d1.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d1) + .isEqualTo(true); + + assertThat(codec.decode(d2.content(), d2.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d2) + .isEqualTo(false); + + assertThat(codec.decode(d3.content(), d3.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d3) + .isEqualTo(true); + + assertThat(codec.decode(d4.content(), d4.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d4) + .isEqualTo(false); + + assertThat(codec.decode(d5.content(), d5.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d5) + .isEqualTo(true); + + assertThat(codec.decode(d6.content(), d6.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d6) + .isEqualTo(false); + + assertThat(codec.decode(d7.content(), d7.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d7) + .isEqualTo(true); + + assertThat(codec.decode(d8.content(), d8.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d8) + .isEqualTo(false); + + assertThat(codec.decode(d9.content(), d9.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d9) + .isEqualTo(true); + + assertThat(codec.decode(d10.content(), d10.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d10) + .isEqualTo(false); + + assertThatThrownBy(() -> {codec.decode(d11.content(), d11.metadata(), Boolean.class, false, ConnectionContextTest.mock());}) + .isInstanceOf(R2dbcNonTransientException.class); + + assertThat(codec.decode(d12.content(), d12.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d12) + .isEqualTo(true); + + assertThat(codec.decode(d13.content(), d13.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d13) + .isEqualTo(false); + + assertThat(codec.decode(d14.content(), d14.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d14) + .isEqualTo(true); + + assertThat(codec.decode(d15.content(), d15.metadata(), Boolean.class, true, ConnectionContextTest.mock())) + .as("Decode failed, %s", d15) + .isEqualTo(true); + + assertThat(codec.decode(d16.content(), d16.metadata(), Boolean.class, true, ConnectionContextTest.mock())) + .as("Decode failed, %s", d16) + .isEqualTo(false); + + assertThat(codec.decode(d17.content(), d17.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d17) + .isEqualTo(true); + + assertThat(codec.decode(d18.content(), d18.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d18) + .isEqualTo(false); + + assertThat(codec.decode(d19.content(), d19.metadata(), Boolean.class, false, ConnectionContextTest.mock())) + .as("Decode failed, %s", d19) + .isEqualTo(false); + } } From 07bae6206bc3817c6f2c3aa014248040e5a3a968 Mon Sep 17 00:00:00 2001 From: jchrys Date: Wed, 1 Jan 2025 10:44:35 +0900 Subject: [PATCH 15/42] Update GH Actions (#295) Motivation: Need to update GH Actions before deprecation. Modifications: Updates setup-java, upload-artifact, checkout to v4 Results: Up to date --- .github/workflows/cd-release.yml | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cd-release.yml b/.github/workflows/cd-release.yml index 74846be7a..afe780792 100644 --- a/.github/workflows/cd-release.yml +++ b/.github/workflows/cd-release.yml @@ -21,13 +21,13 @@ jobs: prepare: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - + - uses: actions/checkout@v4 - name: Set Up Java 8 - uses: actions/setup-java@v3 + + uses: actions/setup-java@v4 with: - distribution: 'temurin' # gh runner local caches lts temurins - java-version: '8' + distribution: "temurin" # gh runner local caches lts temurins + java-version: "8" - name: Setup Git Configs run: | @@ -63,7 +63,7 @@ jobs: run: ./.github/scripts/ensure_prepared.sh - name: Upload workspace - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: prepare-workspace path: ${{ github.workspace }} @@ -77,7 +77,7 @@ jobs: needs: prepare steps: - name: Download workspace - uses: actions/download-artifact@v4.1.7 + uses: actions/download-artifact@v4 with: name: prepare-workspace path: ./prepare-workspace/ @@ -88,10 +88,10 @@ jobs: chmod 755 ./prepare-workspace/.github/scripts/release_rollback.sh - name: Set up Java 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'temurin' # gh runner local caches lts temurins - java-version: '8' + distribution: "temurin" # gh runner local caches lts temurins + java-version: "8" - name: Setup git configs run: | @@ -123,7 +123,7 @@ jobs: - name: Create Local Deploy Directory run: mkdir -p ~/local-staging - + - name: Prepare Internal Dependencies working-directory: ./prepare-workspace/ run: ./mvnw -B -ntp -pl build-tools clean install -DskipTests -Dcheckstyle.skip @@ -131,16 +131,14 @@ jobs: - name: Import GPG & Deploy Local Staging working-directory: ./prepare-workspace/ run: | - cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import - ./mvnw -B -ntp -pl r2dbc-mysql clean javadoc:jar package gpg:sign org.sonatype.plugins:nexus-staging-maven-plugin:deploy -DnexusUrl=https://s01.oss.sonatype.org -DserverId=ossrh-staging -DaltStagingDirectory=/home/runner/local-staging -DskipRemoteStaging=true -DskipTests=true -Dcheckstyle.skip -Dgpg.passphrase="${{ secrets.GPG_PASSPHRASE }}" -Dgpg.keyname="${{ secrets.GPG_KEY_NAME }}" + cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import + ./mvnw -B -ntp -pl r2dbc-mysql clean javadoc:jar package gpg:sign org.sonatype.plugins:nexus-staging-maven-plugin:deploy -DnexusUrl=https://s01.oss.sonatype.org -DserverId=ossrh-staging -DaltStagingDirectory=/home/runner/local-staging -DskipRemoteStaging=true -DskipTests=true -Dcheckstyle.skip -Dgpg.passphrase="${{ secrets.GPG_PASSPHRASE }}" -Dgpg.keyname="${{ secrets.GPG_KEY_NAME }}" - name: Deploy Local Staged Artifacts working-directory: ./prepare-workspace/ run: ./mvnw -B -ntp -pl r2dbc-mysql --file pom.xml org.sonatype.plugins:nexus-staging-maven-plugin:deploy-staged -DnexusUrl=https://s01.oss.sonatype.org -DserverId=ossrh-staging -DaltStagingDirectory=/home/runner/local-staging -DskipStagingRepositoryClose=true -Dcheckstyle.skip - - name: Rollback Release working-directory: ./prepare-workspace/ if: ${{ failure() }} run: ./.github/scripts/release_rollback.sh trunk - From 0a8eb03419bf12b487adc3d259b3482ed36fb163 Mon Sep 17 00:00:00 2001 From: jchrys Date: Wed, 1 Jan 2025 11:16:23 +0900 Subject: [PATCH 16/42] Fix Release Action (#296) Motivation: Release action was broken. Modifications: includes hidden files when uploading arfiacts Results: Bug fixed --- .github/workflows/cd-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cd-release.yml b/.github/workflows/cd-release.yml index afe780792..a57a2e58e 100644 --- a/.github/workflows/cd-release.yml +++ b/.github/workflows/cd-release.yml @@ -67,6 +67,8 @@ jobs: with: name: prepare-workspace path: ${{ github.workspace }} + include-hidden-files: true + - name: Rollback Release working-directory: ./prepare-workspace/ if: ${{ failure() }} From aa15d0bc6570dc09dc9f20a6cdc012afc8db8309 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Wed, 1 Jan 2025 02:23:09 +0000 Subject: [PATCH 17/42] [maven-release-plugin] prepare release r2dbc-mysql-1.3.1 --- r2dbc-mysql/pom.xml | 4 ++-- test-native-image/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 64d20b48b..13bd87dbf 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.3.1-SNAPSHOT + 1.3.1 Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - HEAD + r2dbc-mysql-1.3.1 diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index 36ccf08e3..9cb428266 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.3.1-SNAPSHOT + 1.3.2-SNAPSHOT UTF-8 From 3bd23bedc210b8a6f6874b278f724ffb7f59b35a Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Wed, 1 Jan 2025 02:23:11 +0000 Subject: [PATCH 18/42] [maven-release-plugin] prepare for next development iteration --- r2dbc-mysql/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 13bd87dbf..cb6a35607 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.3.1 + 1.3.2-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - r2dbc-mysql-1.3.1 + HEAD From 9d9e763a3b0a4de2f7009ebb4374c3a7ea1fb825 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 12 Jan 2025 04:26:53 +0900 Subject: [PATCH 19/42] Update README (#297) Motivation: Released 1.3.1 Modifications: Released 1.3.1 Result: up-to-date --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e581a7159..2a9e75b77 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Reactive Relational Database Connectivity MySQL Implementation + ![Maven Central](https://img.shields.io/maven-central/v/io.asyncer/r2dbc-mysql?color=blue) ![LICENSE](https://img.shields.io/github/license/asyncer-io/r2dbc-mysql) @@ -10,15 +11,17 @@ delegate to. See [R2DBC Homepage](https://r2dbc.io). See [R2DBC MySQL wiki](https://github.com/asyncer-io/r2dbc-mysql/wiki) for more information. ## Spring-framework and R2DBC-SPI Compatibility + Refer to the table below to determine the appropriate version of r2dbc-mysql for your project. | spring-boot-starter-data-r2dbc | spring-data-r2dbc | r2dbc-spi | r2dbc-mysql(recommended) | |--------------------------------|-------------------|---------------|------------------------------| -| 3.0.* and above | 3.0.* and above | 1.0.0.RELEASE | io.asyncer:r2dbc-mysql:1.2.0 | +| 3.0.* and above | 3.0.* and above | 1.0.0.RELEASE | io.asyncer:r2dbc-mysql:1.3.1 | | 2.7.* | 1.5.* | 0.9.1.RELEASE | io.asyncer:r2dbc-mysql:0.9.7 | | 2.6.* and below | 1.4.* and below | 0.8.6.RELEASE | dev.miku:r2dbc-mysql:0.8.2 | ## Supported Features + This driver provides the following features: - [x] Unix domain socket. @@ -37,6 +40,7 @@ This driver provides the following features: - [x] MariaDB `RETURNING` clause. ## Version compatibility / Integration tests states + ![MySQL 5.5 status](https://img.shields.io/badge/MySQL%205.5-pass-blue) ![MySQL 5.6 status](https://img.shields.io/badge/MySQL%205.6-pass-blue) ![MySQL 5.7 status](https://img.shields.io/badge/MySQL%205.7-pass-blue) @@ -49,7 +53,6 @@ This driver provides the following features: ![MariaDB 10.6 status](https://img.shields.io/badge/MariaDB%2010.6-pass-blue) ![MariaDB 10.11 status](https://img.shields.io/badge/MariaDB%2010.11-pass-blue) - In fact, it supports lower versions, in the theory, such as 4.1, 4.0, etc. However, Docker-certified images do not have these versions lower than 5.5.0, so tests are not integrated on these versions. @@ -61,7 +64,7 @@ However, Docker-certified images do not have these versions lower than 5.5.0, so io.asyncer r2dbc-mysql - 1.2.0 + 1.3.1 ``` @@ -71,7 +74,7 @@ However, Docker-certified images do not have these versions lower than 5.5.0, so ```groovy dependencies { - implementation 'io.asyncer:r2dbc-mysql:1.2.0' + implementation 'io.asyncer:r2dbc-mysql:1.3.1' } ``` @@ -80,7 +83,7 @@ dependencies { ```kotlin dependencies { // Maybe should to use `compile` instead of `implementation` on the lower version of Gradle. - implementation("io.asyncer:r2dbc-mysql:1.2.0") + implementation("io.asyncer:r2dbc-mysql:1.3.1") } ``` @@ -113,7 +116,7 @@ See [Usage](https://github.com/asyncer-io/r2dbc-mysql/wiki/usage) wiki for more ## Reporting Issues -The R2DBC MySQL Implementation uses GitHub as issue tracking system to record bugs and feature requests. +The R2DBC MySQL Implementation uses GitHub as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below: - Before log a bug, please search the [issue tracker](https://github.com/asyncer-io/r2dbc-mysql/issues) to see if someone has already reported the problem. @@ -152,7 +155,7 @@ Thanks a lot for your support! ## Supports -- [R2DBC Team](https://r2dbc.io) - Thanks for their support by sharing all relevant resources around R2DBC +- [R2DBC Team](https://r2dbc.io) - Thanks for their support by sharing all relevant resources around R2DBC projects. [m]: https://www.mysql.com From d2854390d7c2fd3e75e7c2b884fe4c906126de4e Mon Sep 17 00:00:00 2001 From: rxdcxdrnine Date: Sat, 18 Jan 2025 17:47:01 +0900 Subject: [PATCH 20/42] Add Metrics Configuration Support to enable TcpClient metrics --- .../mysql/MySqlConnectionConfiguration.java | 42 ++++++++++++++++--- .../r2dbc/mysql/MySqlConnectionFactory.java | 3 +- .../mysql/MySqlConnectionFactoryProvider.java | 12 ++++++ .../io/asyncer/r2dbc/mysql/client/Client.java | 6 ++- .../MySqlConnectionConfigurationTest.java | 13 ++++++ .../MySqlConnectionFactoryProviderTest.java | 13 ++++++ 6 files changed, 81 insertions(+), 8 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java index 2f1c75961..8b4c789de 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java @@ -25,6 +25,7 @@ import io.netty.resolver.AddressResolverGroup; import org.jetbrains.annotations.Nullable; import org.reactivestreams.Publisher; +import reactor.netty.internal.util.Metrics; import reactor.netty.resources.LoopResources; import reactor.netty.tcp.TcpResources; @@ -131,6 +132,8 @@ public final class MySqlConnectionConfiguration { @Nullable private final AddressResolverGroup resolver; + private final boolean metrics; + private MySqlConnectionConfiguration( boolean isHost, String domain, int port, MySqlSslConfiguration ssl, boolean tcpKeepAlive, boolean tcpNoDelay, @Nullable Duration connectTimeout, @@ -146,7 +149,8 @@ private MySqlConnectionConfiguration( Set compressionAlgorithms, int zstdCompressionLevel, @Nullable LoopResources loopResources, Extensions extensions, @Nullable Publisher passwordPublisher, - @Nullable AddressResolverGroup resolver + @Nullable AddressResolverGroup resolver, + boolean metrics ) { this.isHost = isHost; this.domain = domain; @@ -177,6 +181,7 @@ private MySqlConnectionConfiguration( this.extensions = extensions; this.passwordPublisher = passwordPublisher; this.resolver = resolver; + this.metrics = metrics; } /** @@ -312,6 +317,10 @@ AddressResolverGroup getResolver() { return resolver; } + boolean isMetrics() { + return metrics; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -349,7 +358,8 @@ public boolean equals(Object o) { Objects.equals(loopResources, that.loopResources) && extensions.equals(that.extensions) && Objects.equals(passwordPublisher, that.passwordPublisher) && - Objects.equals(resolver, that.resolver); + Objects.equals(resolver, that.resolver) && + metrics == that.metrics; } @Override @@ -364,7 +374,7 @@ public int hashCode() { loadLocalInfilePath, localInfileBufferSize, queryCacheSize, prepareCacheSize, compressionAlgorithms, zstdCompressionLevel, - loopResources, extensions, passwordPublisher, resolver); + loopResources, extensions, passwordPublisher, resolver, metrics); } @Override @@ -398,7 +408,8 @@ private String buildCommonToStringPart() { ", loopResources=" + loopResources + ", extensions=" + extensions + ", passwordPublisher=" + passwordPublisher + - ", resolver=" + resolver; + ", resolver=" + resolver + + ", metrics=" + metrics; } /** @@ -498,6 +509,8 @@ public static final class Builder { @Nullable private AddressResolverGroup resolver; + private boolean metrics; + /** * Builds an immutable {@link MySqlConnectionConfiguration} with current options. * @@ -532,7 +545,7 @@ public MySqlConnectionConfiguration build() { loadLocalInfilePath, localInfileBufferSize, queryCacheSize, prepareCacheSize, compressionAlgorithms, zstdCompressionLevel, loopResources, - Extensions.from(extensions, autodetectExtensions), passwordPublisher, resolver); + Extensions.from(extensions, autodetectExtensions), passwordPublisher, resolver, metrics); } /** @@ -1175,6 +1188,25 @@ public Builder resolver(AddressResolverGroup resolver) { return this; } + /** + * Option to enable metrics to be collected and registered in Micrometer's globalRegistry + * with {@link reactor.netty.tcp.TcpClient#metrics(boolean)}. Defaults to {@code false}. + *

+ * Note: It is required to add {@code io.micrometer.micrometer-core} dependency to classpath. + * + * @param enabled enable metrics for {@link reactor.netty.tcp.TcpClient}. + * @return this {@link Builder} + * @throws IllegalArgumentException if {@code io.micrometer:micrometer-core} is not on the classpath. + * @since 1.3.2 + */ + public Builder metrics(boolean enabled) { + require(!enabled || Metrics.isMicrometerAvailable(), + "dependency `io.micrometer:micrometer-core` must be added to classpath if metrics enabled" + ); + this.metrics = enabled; + return this; + } + private SslMode requireSslMode() { SslMode sslMode = this.sslMode; diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java index bff85c809..e483d2d6d 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java @@ -148,7 +148,8 @@ private static Mono getMySqlConnection( context, configuration.getConnectTimeout(), configuration.getLoopResources(), - configuration.getResolver() + configuration.getResolver(), + configuration.isMetrics() )).flatMap(client -> { // Lazy init database after handshake/login boolean deferDatabase = configuration.isCreateDatabaseIfNotExist(); diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java index d89005394..8f045fcad 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java @@ -320,6 +320,16 @@ public final class MySqlConnectionFactoryProvider implements ConnectionFactoryPr */ public static final Option> RESOLVER = Option.valueOf("resolver"); + /** + * Option to enable metrics to be collected and registered in Micrometer's globalRegistry + * with {@link reactor.netty.tcp.TcpClient#metrics(boolean)}. Defaults to {@code false}. + *

+ * Note: It is required to add {@code io.micrometer.micrometer-core} dependency to classpath. + * + * @since 1.3.2 + */ + public static final Option METRICS = Option.valueOf("metrics"); + @Override public ConnectionFactory create(ConnectionFactoryOptions options) { requireNonNull(options, "connectionFactoryOptions must not be null"); @@ -413,6 +423,8 @@ static MySqlConnectionConfiguration setup(ConnectionFactoryOptions options) { .to(builder::lockWaitTimeout); mapper.optional(STATEMENT_TIMEOUT).as(Duration.class, Duration::parse) .to(builder::statementTimeout); + mapper.optional(METRICS).asBoolean() + .to(builder::metrics); return builder.build(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java index 0beaf4c0d..316d90999 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java @@ -127,19 +127,21 @@ public interface Client { * @param context the connection context * @param connectTimeout connect timeout, or {@code null} if it has no timeout * @param loopResources the loop resources to use + * @param metrics if enable the {@link TcpClient#metrics)} * @return A {@link Mono} that will emit a connected {@link Client}. * @throws IllegalArgumentException if {@code ssl}, {@code address} or {@code context} is {@code null}. * @throws ArithmeticException if {@code connectTimeout} milliseconds overflow as an int */ static Mono connect(MySqlSslConfiguration ssl, SocketAddress address, boolean tcpKeepAlive, boolean tcpNoDelay, ConnectionContext context, @Nullable Duration connectTimeout, - LoopResources loopResources, @Nullable AddressResolverGroup resolver) { + LoopResources loopResources, @Nullable AddressResolverGroup resolver, boolean metrics) { requireNonNull(ssl, "ssl must not be null"); requireNonNull(address, "address must not be null"); requireNonNull(context, "context must not be null"); TcpClient tcpClient = TcpClient.newConnection() - .runOn(loopResources); + .runOn(loopResources) + .metrics(metrics); if (connectTimeout != null) { tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java index f05defb17..e62fea190 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java @@ -222,6 +222,19 @@ void validResolver() { assertThat(resolverGroup).isSameAs(resolver); } + @Test + void invalidMetrics() { + // throw exception when metrics true without micrometer-core dependency + assertThatIllegalArgumentException().isThrownBy(() -> + MySqlConnectionConfiguration + .builder() + .host(HOST) + .user(USER) + .metrics(true) + .build() + ); + } + private static MySqlConnectionConfiguration unixSocketSslMode(SslMode sslMode) { return MySqlConnectionConfiguration.builder() .unixSocket(UNIX_SOCKET) diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java index 1e71a9f17..be48a2255 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java @@ -51,6 +51,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.METRICS; import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.PASSWORD_PUBLISHER; import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.RESOLVER; import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.USE_SERVER_PREPARE_STATEMENT; @@ -469,6 +470,18 @@ void validResolver() { assertThat(ConnectionFactories.get(options)).isExactlyInstanceOf(MySqlConnectionFactory.class); } + @Test + void invalidMetrics() { + // throw exception when metrics true without micrometer-core dependency + assertThatIllegalArgumentException().isThrownBy(() -> + ConnectionFactories.get(ConnectionFactoryOptions.builder() + .option(DRIVER, "mysql") + .option(HOST, "127.0.0.1") + .option(USER, "root") + .option(METRICS, true) + .build())); + } + @Test void allConfigurationOptions() { List exceptConfigs = Arrays.asList( From 7b28fe37705af953e8ff23b5ade316ad9afeec53 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Thu, 23 Jan 2025 20:23:36 +0000 Subject: [PATCH 21/42] [maven-release-plugin] prepare release r2dbc-mysql-1.3.2 --- r2dbc-mysql/pom.xml | 4 ++-- test-native-image/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index cb6a35607..a4adcd057 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.3.2-SNAPSHOT + 1.3.2 Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - HEAD + r2dbc-mysql-1.3.2 diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index 9cb428266..412e2a616 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.3.2-SNAPSHOT + 1.3.3-SNAPSHOT UTF-8 From 820bdf47e06c8c4f55eb7b8c792b871a8c461b0e Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Thu, 23 Jan 2025 20:23:39 +0000 Subject: [PATCH 22/42] [maven-release-plugin] prepare for next development iteration --- r2dbc-mysql/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index a4adcd057..2c16d5055 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.3.2 + 1.3.3-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - r2dbc-mysql-1.3.2 + HEAD From abaa2d1a4703ddd754d2c2fc5d1a4ce8f5039e66 Mon Sep 17 00:00:00 2001 From: jchrys Date: Fri, 31 Jan 2025 20:44:36 +0900 Subject: [PATCH 23/42] Support `tinyInt1isBit` Motivation: Aligning with MySQL connector. Modifications: Implemented `tinyInt1isBit` flag. Result: Improved compatibility with MySQL connectors. --- r2dbc-mysql/pom.xml | 2 +- .../r2dbc/mysql/ConnectionContext.java | 9 +++ .../mysql/MySqlConnectionConfiguration.java | 69 +++++++++++++------ .../r2dbc/mysql/MySqlConnectionFactory.java | 1 + .../mysql/MySqlConnectionFactoryProvider.java | 13 +++- .../r2dbc/mysql/codec/CodecContext.java | 6 ++ .../r2dbc/mysql/codec/DefaultCodecs.java | 23 +++++-- .../r2dbc/mysql/ConnectionContextTest.java | 8 +-- .../mysql/ConnectionIntegrationTest.java | 25 +++++++ .../r2dbc/mysql/TinyInt1isBitFalseTest.java | 41 +++++++++++ test-native-image/pom.xml | 2 +- 11 files changed, 165 insertions(+), 34 deletions(-) create mode 100644 r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TinyInt1isBitFalseTest.java diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 2c16d5055..43d118836 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.3.3-SNAPSHOT + 1.4.0-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionContext.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionContext.java index cc5aeb2a9..26ec660c4 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionContext.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionContext.java @@ -51,6 +51,8 @@ public final class ConnectionContext implements CodecContext { private final int localInfileBufferSize; + private final boolean tinyInt1isBit; + private final boolean preserveInstants; private int connectionId = -1; @@ -107,12 +109,14 @@ public final class ConnectionContext implements CodecContext { ZeroDateOption zeroDateOption, @Nullable Path localInfilePath, int localInfileBufferSize, + boolean tinyInt1isBit, boolean preserveInstants, @Nullable ZoneId timeZone ) { this.zeroDateOption = requireNonNull(zeroDateOption, "zeroDateOption must not be null"); this.localInfilePath = localInfilePath; this.localInfileBufferSize = localInfileBufferSize; + this.tinyInt1isBit = tinyInt1isBit; this.preserveInstants = preserveInstants; this.timeZone = timeZone; } @@ -216,6 +220,11 @@ public boolean isMariaDb() { return (capability != null && capability.isMariaDb()) || serverVersion.isMariaDb(); } + @Override + public boolean isTinyInt1isBit() { + return tinyInt1isBit; + } + public boolean isNoBackslashEscapes() { return (serverStatuses & ServerStatuses.NO_BACKSLASH_ESCAPES) != 0; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java index 8b4c789de..93ba9ce78 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java @@ -134,24 +134,26 @@ public final class MySqlConnectionConfiguration { private final boolean metrics; + private final boolean tinyInt1isBit; + private MySqlConnectionConfiguration( - boolean isHost, String domain, int port, MySqlSslConfiguration ssl, - boolean tcpKeepAlive, boolean tcpNoDelay, @Nullable Duration connectTimeout, - ZeroDateOption zeroDateOption, - boolean preserveInstants, - String connectionTimeZone, - boolean forceConnectionTimeZoneToSession, - String user, @Nullable CharSequence password, @Nullable String database, - boolean createDatabaseIfNotExist, @Nullable Predicate preferPrepareStatement, - List sessionVariables, @Nullable Duration lockWaitTimeout, @Nullable Duration statementTimeout, - @Nullable Path loadLocalInfilePath, int localInfileBufferSize, - int queryCacheSize, int prepareCacheSize, - Set compressionAlgorithms, int zstdCompressionLevel, - @Nullable LoopResources loopResources, - Extensions extensions, @Nullable Publisher passwordPublisher, - @Nullable AddressResolverGroup resolver, - boolean metrics - ) { + boolean isHost, String domain, int port, MySqlSslConfiguration ssl, + boolean tcpKeepAlive, boolean tcpNoDelay, @Nullable Duration connectTimeout, + ZeroDateOption zeroDateOption, + boolean preserveInstants, + String connectionTimeZone, + boolean forceConnectionTimeZoneToSession, + String user, @Nullable CharSequence password, @Nullable String database, + boolean createDatabaseIfNotExist, @Nullable Predicate preferPrepareStatement, + List sessionVariables, @Nullable Duration lockWaitTimeout, @Nullable Duration statementTimeout, + @Nullable Path loadLocalInfilePath, int localInfileBufferSize, + int queryCacheSize, int prepareCacheSize, + Set compressionAlgorithms, int zstdCompressionLevel, + @Nullable LoopResources loopResources, + Extensions extensions, @Nullable Publisher passwordPublisher, + @Nullable AddressResolverGroup resolver, + boolean metrics, + boolean tinyInt1isBit) { this.isHost = isHost; this.domain = domain; this.port = port; @@ -182,6 +184,7 @@ private MySqlConnectionConfiguration( this.passwordPublisher = passwordPublisher; this.resolver = resolver; this.metrics = metrics; + this.tinyInt1isBit = tinyInt1isBit; } /** @@ -321,6 +324,10 @@ boolean isMetrics() { return metrics; } + boolean isTinyInt1isBit() { + return tinyInt1isBit; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -359,7 +366,8 @@ public boolean equals(Object o) { extensions.equals(that.extensions) && Objects.equals(passwordPublisher, that.passwordPublisher) && Objects.equals(resolver, that.resolver) && - metrics == that.metrics; + metrics == that.metrics && + tinyInt1isBit == that.tinyInt1isBit; } @Override @@ -374,7 +382,7 @@ public int hashCode() { loadLocalInfilePath, localInfileBufferSize, queryCacheSize, prepareCacheSize, compressionAlgorithms, zstdCompressionLevel, - loopResources, extensions, passwordPublisher, resolver, metrics); + loopResources, extensions, passwordPublisher, resolver, metrics, tinyInt1isBit); } @Override @@ -409,7 +417,8 @@ private String buildCommonToStringPart() { ", extensions=" + extensions + ", passwordPublisher=" + passwordPublisher + ", resolver=" + resolver + - ", metrics=" + metrics; + ", metrics=" + metrics + + ", tinyint1isBit=" + tinyInt1isBit; } /** @@ -511,6 +520,8 @@ public static final class Builder { private boolean metrics; + private boolean tinyInt1isBit = true; + /** * Builds an immutable {@link MySqlConnectionConfiguration} with current options. * @@ -545,11 +556,11 @@ public MySqlConnectionConfiguration build() { loadLocalInfilePath, localInfileBufferSize, queryCacheSize, prepareCacheSize, compressionAlgorithms, zstdCompressionLevel, loopResources, - Extensions.from(extensions, autodetectExtensions), passwordPublisher, resolver, metrics); + Extensions.from(extensions, autodetectExtensions), passwordPublisher, resolver, metrics, tinyInt1isBit); } /** - * Configures the database. Default no database. + * Configures the database. Default no database. * * @param database the database, or {@code null} if no database want to be login. * @return this {@link Builder}. @@ -1207,6 +1218,20 @@ public Builder metrics(boolean enabled) { return this; } + /** + * Option to whether the driver should interpret MySQL's TINYINT(1) as a BIT type. + * When enabled, TINYINT(1) columns (both SIGNED and UNSIGNED) will be treated as + * BIT. default to {@code true}. + * + * @param tinyInt1isBit {@code true} to treat TINYINT(1) as BIT + * @return this {@link Builder} + * @since 1.4.0 + */ + public Builder tinyInt1isBit(boolean tinyInt1isBit) { + this.tinyInt1isBit = tinyInt1isBit; + return this; + } + private SslMode requireSslMode() { SslMode sslMode = this.sslMode; diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java index e483d2d6d..a6880cc82 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java @@ -137,6 +137,7 @@ private static Mono getMySqlConnection( configuration.getZeroDateOption(), configuration.getLoadLocalInfilePath(), configuration.getLocalInfileBufferSize(), + configuration.isTinyInt1isBit(), configuration.isPreserveInstants(), connectionTimeZone ); diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java index 8f045fcad..bff335123 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java @@ -330,6 +330,15 @@ public final class MySqlConnectionFactoryProvider implements ConnectionFactoryPr */ public static final Option METRICS = Option.valueOf("metrics"); + /** + * Option to whether the driver should interpret MySQL's TINYINT(1) as a BIT type. + * When enabled, TINYINT(1) columns (both SIGNED and UNSIGNED) will be treated as + * BIT. default to {@code true}. + * + * @since 1.4.0 + */ + public static final Option TINY_INT_1_IS_BIT = Option.valueOf("tinyInt1isBit"); + @Override public ConnectionFactory create(ConnectionFactoryOptions options) { requireNonNull(options, "connectionFactoryOptions must not be null"); @@ -424,7 +433,9 @@ static MySqlConnectionConfiguration setup(ConnectionFactoryOptions options) { mapper.optional(STATEMENT_TIMEOUT).as(Duration.class, Duration::parse) .to(builder::statementTimeout); mapper.optional(METRICS).asBoolean() - .to(builder::metrics); + .to(builder::metrics); + mapper.optional(TINY_INT_1_IS_BIT).asBoolean() + .to(builder::tinyInt1isBit); return builder.build(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/CodecContext.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/CodecContext.java index 8eda9c985..5b58fa5f6 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/CodecContext.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/CodecContext.java @@ -69,4 +69,10 @@ public interface CodecContext { * @return if is MariaDB. */ boolean isMariaDb(); + + /** + * + * @return true if tinyInt(1) is treated as bit. + */ + boolean isTinyInt1isBit(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java index 4542a7c9d..ba022875a 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java @@ -45,6 +45,8 @@ */ final class DefaultCodecs implements Codecs { + private static final Integer INTEGER_ONE = Integer.valueOf(1); + private static final List> DEFAULT_CODECS = InternalArrays.asImmutableList( ByteCodec.INSTANCE, ShortCodec.INSTANCE, @@ -137,6 +139,7 @@ private DefaultCodecs(List> codecs) { * Note: this method should NEVER release {@code buf} because of it come from {@code MySqlRow} which will release * this buffer. */ + @Nullable @Override public T decode(FieldValue value, MySqlReadableMetadata metadata, Class type, boolean binary, CodecContext context) { @@ -151,7 +154,7 @@ public T decode(FieldValue value, MySqlReadableMetadata metadata, Class t return null; } - Class target = chooseClass(metadata, type); + Class target = chooseClass(metadata, type, context); if (value instanceof NormalFieldValue) { return decodeNormal((NormalFieldValue) value, metadata, target, binary, context); @@ -162,6 +165,7 @@ public T decode(FieldValue value, MySqlReadableMetadata metadata, Class t throw new IllegalArgumentException("Unknown value " + value.getClass().getSimpleName()); } + @Nullable @Override public T decode(FieldValue value, MySqlReadableMetadata metadata, ParameterizedType type, boolean binary, CodecContext context) { @@ -359,18 +363,27 @@ private T decodeMassive(LargeFieldValue value, MySqlReadableMetadata metadat * @param type the {@link Class} specified by the user. * @return the {@link Class} to use for decoding. */ - private static Class chooseClass(final MySqlReadableMetadata metadata, Class type) { - final Class javaType = getDefaultJavaType(metadata); + private static Class chooseClass(final MySqlReadableMetadata metadata, Class type, + final CodecContext codecContext) { + final Class javaType = getDefaultJavaType(metadata, codecContext); return type.isAssignableFrom(javaType) ? javaType : type; } - private static Class getDefaultJavaType(final MySqlReadableMetadata metadata) { + private static Class getDefaultJavaType(final MySqlReadableMetadata metadata, final CodecContext codecContext) { final MySqlType type = metadata.getType(); + final Integer precision = metadata.getPrecision(); + + if (INTEGER_ONE.equals(precision) && (type == MySqlType.TINYINT || type == MySqlType.TINYINT_UNSIGNED) + && codecContext.isTinyInt1isBit()) { + return Boolean.class; + } + // ref: https://github.com/asyncer-io/r2dbc-mysql/issues/277 // BIT(1) should be treated as Boolean by default. - if (type == MySqlType.BIT && Integer.valueOf(1).equals(metadata.getPrecision())) { + if (INTEGER_ONE.equals(precision) && type == MySqlType.BIT) { return Boolean.class; } + return type.getJavaType(); } diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionContextTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionContextTest.java index 5d0635412..eb4a3ea3c 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionContextTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionContextTest.java @@ -39,7 +39,7 @@ void getTimeZone() { String id = i < 0 ? "UTC" + i : "UTC+" + i; ConnectionContext context = new ConnectionContext( ZeroDateOption.USE_NULL, null, - 8192, true, ZoneId.of(id)); + 8192, true, true, ZoneId.of(id)); assertThat(context.getTimeZone()).isEqualTo(ZoneId.of(id)); } @@ -48,7 +48,7 @@ void getTimeZone() { @Test void setTwiceTimeZone() { ConnectionContext context = new ConnectionContext(ZeroDateOption.USE_NULL, null, - 8192, true, null); + 8192, true, true, null); context.initSession( Caches.createPrepareCache(0), @@ -70,7 +70,7 @@ void setTwiceTimeZone() { @Test void badSetTimeZone() { ConnectionContext context = new ConnectionContext(ZeroDateOption.USE_NULL, null, - 8192, true, ZoneId.systemDefault()); + 8192, true, true, ZoneId.systemDefault()); assertThatIllegalStateException().isThrownBy(() -> context.initSession( Caches.createPrepareCache(0), IsolationLevel.REPEATABLE_READ, @@ -91,7 +91,7 @@ public static ConnectionContext mock(boolean isMariaDB) { public static ConnectionContext mock(boolean isMariaDB, ZoneId zoneId) { ConnectionContext context = new ConnectionContext(ZeroDateOption.USE_NULL, null, - 8192, true, zoneId); + 8192, true, true, zoneId); context.initHandshake(1, ServerVersion.parse(isMariaDB ? "11.2.22.MOCKED" : "8.0.11.MOCKED"), Capability.of(~(isMariaDB ? 1 : 0))); diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java index b65b3b447..fccd38530 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java @@ -579,6 +579,31 @@ void loadDataLocalInfile(String name) throws URISyntaxException, IOException { .doOnNext(it -> assertThat(it).isEqualTo(json))); } + @Test + public void tinyInt1isBitTrueTestValue1() { + complete(connection -> Mono.from(connection.createStatement("CREATE TEMPORARY TABLE `test` (`id` INT NOT NULL PRIMARY KEY, `value` TINYINT(1))").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("INSERT INTO `test` VALUES (1, 1)").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("SELECT `value` FROM `test`").execute()) + .flatMap(result -> result.map((row, metadata) -> row.get("value", Object.class))) + .doOnNext(value -> assertThat(value).isInstanceOf(Boolean.class)) + .doOnNext(value -> assertThat(value).isEqualTo(true)) + ); + } + + @Test + public void tinyInt1isBitTrueTestValue0() { + complete(connection -> Mono.from(connection.createStatement("CREATE TEMPORARY TABLE `test` (`id` INT NOT NULL PRIMARY KEY, `value` TINYINT(1))").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("INSERT INTO `test` VALUES (1, 0)").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("SELECT `value` FROM `test`").execute()) + .flatMap(result -> result.map((row, metadata) -> row.get("value", Object.class))) + .doOnNext(value -> assertThat(value).isInstanceOf(Boolean.class)) + .doOnNext(value -> assertThat(value).isEqualTo(false))); + } + @Test void batchCrud() { // TODO: spilt it to multiple test cases and move it to BatchIntegrationTest diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TinyInt1isBitFalseTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TinyInt1isBitFalseTest.java new file mode 100644 index 000000000..9db19e414 --- /dev/null +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TinyInt1isBitFalseTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2025 asyncer.io projects + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.asyncer.r2dbc.mysql; + + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.assertj.core.api.Assertions.assertThat; + +class TinyInt1isBitFalseTest extends IntegrationTestSupport{ + TinyInt1isBitFalseTest() { + super(configuration(builder -> builder.tinyInt1isBit(false))); + } + + @Test + public void tinyInt1isBitFalse() { + complete(connection -> Mono.from(connection.createStatement("CREATE TEMPORARY TABLE `test` (`id` INT NOT NULL PRIMARY KEY, `value` TINYINT(1))").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("INSERT INTO `test` VALUES (1, 1)").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("SELECT `value` FROM `test`").execute()) + .flatMap(result -> result.map((row, metadata) -> row.get("value", Object.class))) + .doOnNext(value -> assertThat(value).isInstanceOf(Byte.class))); + } + +} diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index 412e2a616..1640175cb 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.3.3-SNAPSHOT + 1.4.0-SNAPSHOT UTF-8 From 0764d05232122dca255ef215973391533b077e1d Mon Sep 17 00:00:00 2001 From: John Niang Date: Mon, 10 Feb 2025 11:52:05 +0800 Subject: [PATCH 24/42] Refactor create method of MySQLConnectionFactory --- .../r2dbc/mysql/MySqlConnectionFactory.java | 81 +++++++++---------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java index a6880cc82..094674f2a 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java @@ -42,68 +42,65 @@ */ public final class MySqlConnectionFactory implements ConnectionFactory { - private final Mono client; + private final MySqlConnectionConfiguration configuration; + private final LazyQueryCache queryCache; - private MySqlConnectionFactory(Mono client) { - this.client = client; + private MySqlConnectionFactory(MySqlConnectionConfiguration configuration) { + this.configuration = configuration; + this.queryCache = new LazyQueryCache(configuration.getQueryCacheSize()); } @Override public Mono create() { - return client; - } - - @Override - public ConnectionFactoryMetadata getMetadata() { - return MySqlConnectionFactoryMetadata.INSTANCE; - } + MySqlSslConfiguration ssl; + SocketAddress address; - /** - * Creates a {@link MySqlConnectionFactory} with a {@link MySqlConnectionConfiguration}. - * - * @param configuration the {@link MySqlConnectionConfiguration}. - * @return configured {@link MySqlConnectionFactory}. - */ - public static MySqlConnectionFactory from(MySqlConnectionConfiguration configuration) { - requireNonNull(configuration, "configuration must not be null"); - - LazyQueryCache queryCache = new LazyQueryCache(configuration.getQueryCacheSize()); - - return new MySqlConnectionFactory(Mono.defer(() -> { - MySqlSslConfiguration ssl; - SocketAddress address; - - if (configuration.isHost()) { - ssl = configuration.getSsl(); - address = InetSocketAddress.createUnresolved(configuration.getDomain(), + if (configuration.isHost()) { + ssl = configuration.getSsl(); + address = InetSocketAddress.createUnresolved(configuration.getDomain(), configuration.getPort()); - } else { - ssl = MySqlSslConfiguration.disabled(); - address = new DomainSocketAddress(configuration.getDomain()); - } + } else { + ssl = MySqlSslConfiguration.disabled(); + address = new DomainSocketAddress(configuration.getDomain()); + } - String user = configuration.getUser(); - CharSequence password = configuration.getPassword(); - Publisher passwordPublisher = configuration.getPasswordPublisher(); + String user = configuration.getUser(); + CharSequence password = configuration.getPassword(); + Publisher passwordPublisher = configuration.getPasswordPublisher(); - if (Objects.nonNull(passwordPublisher)) { - return Mono.from(passwordPublisher).flatMap(token -> getMySqlConnection( + if (Objects.nonNull(passwordPublisher)) { + return Mono.from(passwordPublisher).flatMap(token -> getMySqlConnection( configuration, ssl, queryCache, address, user, token - )); - } + )); + } - return getMySqlConnection( + return getMySqlConnection( configuration, ssl, queryCache, address, user, password - ); - })); + ); + } + + @Override + public ConnectionFactoryMetadata getMetadata() { + return MySqlConnectionFactoryMetadata.INSTANCE; + } + + /** + * Creates a {@link MySqlConnectionFactory} with a {@link MySqlConnectionConfiguration}. + * + * @param configuration the {@link MySqlConnectionConfiguration}. + * @return configured {@link MySqlConnectionFactory}. + */ + public static MySqlConnectionFactory from(MySqlConnectionConfiguration configuration) { + requireNonNull(configuration, "configuration must not be null"); + return new MySqlConnectionFactory(configuration); } /** From a1c99ea5ab9585c7bffff7a20bac1eaf2d7d38aa Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 16 Feb 2025 06:23:27 +0900 Subject: [PATCH 25/42] use ubuntu-latest Motivation: Ubuntu 20.04 runner will be unsupported by april 1. Modification: Switched to ubuntu-latest Result: Ensure future compatibility. --- .github/workflows/ci-graalvm-tests.yml | 14 +++++++------- .github/workflows/ci-integration-tests.yml | 7 ++++--- .../workflows/ci-mariadb-intergration-tests.yml | 7 ++++--- .github/workflows/ci-unit-tests.yml | 8 ++++---- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-graalvm-tests.yml b/.github/workflows/ci-graalvm-tests.yml index ee5f0d03d..54db266ff 100644 --- a/.github/workflows/ci-graalvm-tests.yml +++ b/.github/workflows/ci-graalvm-tests.yml @@ -16,18 +16,18 @@ name: Native Image Build Test on: pull_request: - branches: [ "trunk", "0.9.x" ] + branches: ["trunk", "0.9.x"] jobs: graalvm-build-pr: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: graalvm/setup-graalvm@v1 with: java-version: 21 - distribution: 'graalvm' + distribution: "graalvm" native-image-job-reports: true github-token: ${{ secrets.GITHUB_TOKEN }} @@ -43,7 +43,7 @@ jobs: - name: Build and run native image run: | - echo "JAVA_HOME=$JAVA_HOME" - echo "./mvnw -Pgraalvm package -Dmaven.javadoc.skip=true" - ./mvnw -Pgraalvm package -Dmaven.javadoc.skip=true - ./test-native-image/target/test-native-image + echo "JAVA_HOME=$JAVA_HOME" + echo "./mvnw -Pgraalvm package -Dmaven.javadoc.skip=true" + ./mvnw -Pgraalvm package -Dmaven.javadoc.skip=true + ./test-native-image/target/test-native-image diff --git a/.github/workflows/ci-integration-tests.yml b/.github/workflows/ci-integration-tests.yml index 8cf8991d2..1ef3cdb0e 100644 --- a/.github/workflows/ci-integration-tests.yml +++ b/.github/workflows/ci-integration-tests.yml @@ -2,14 +2,15 @@ name: Integration Tests on: pull_request: - branches: [ "trunk", "0.9.x" ] + branches: ["trunk", "0.9.x"] jobs: integration-tests-pr: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: - mysql-version: [ 5.5, 5.6.45, 5.6, 5.7.28, 5.7, 8.0, 8.1, 8.2, 8.3, 8.4, 9.0] + mysql-version: + [5.5, 5.6.45, 5.6, 5.7.28, 5.7, 8.0, 8.1, 8.2, 8.3, 8.4, 9.0] name: Integration test with MySQL ${{ matrix.mysql-version }} steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ci-mariadb-intergration-tests.yml b/.github/workflows/ci-mariadb-intergration-tests.yml index d20bba01a..25cf0e498 100644 --- a/.github/workflows/ci-mariadb-intergration-tests.yml +++ b/.github/workflows/ci-mariadb-intergration-tests.yml @@ -2,14 +2,15 @@ name: Integration Tests for MariaDB on: pull_request: - branches: [ "trunk", "0.9.x" ] + branches: ["trunk", "0.9.x"] jobs: mariadb-integration-tests-pr: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: - mariadb-version: [ 10.0, 10.1, 10.2.15, 10.2, 10.3.7, 10.3, 10.5.1, 10.5, 10.6, 10.11] + mariadb-version: + [10.0, 10.1, 10.2.15, 10.2, 10.3.7, 10.3, 10.5.1, 10.5, 10.6, 10.11] name: Integration test with MariaDB ${{ matrix.mariadb-version }} steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ci-unit-tests.yml b/.github/workflows/ci-unit-tests.yml index fdf48ce0d..0247ca1d7 100644 --- a/.github/workflows/ci-unit-tests.yml +++ b/.github/workflows/ci-unit-tests.yml @@ -2,14 +2,14 @@ name: Unit tests on: pull_request: - branches: [ "trunk", "0.9.x" ] + branches: ["trunk", "0.9.x"] jobs: unit-tests-pr: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: - java-version: [ 8, 11, 17, 21 ] + java-version: [8, 11, 17, 21] name: linux-java-${{ matrix.java-version }} steps: - uses: actions/checkout@v3 @@ -20,7 +20,7 @@ jobs: java-version: ${{ matrix.java-version }} cache: maven - name: Unit test with Maven - run: | + run: | set -o pipefail ./mvnw -B test -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN \ -Dio.netty.leakDetectionLevel=paranoid \ From badb3e5cbc72473d60e9ab4f6d445c8789ca09eb Mon Sep 17 00:00:00 2001 From: jchrys Date: Mon, 17 Feb 2025 19:33:24 +0900 Subject: [PATCH 26/42] Fix false positive unexpected connection close Motivation: The connection could be incorrectly detected as unexpectedly closed due to a race condition where the exit message was sent before updating the state to `ST_CLOSING`. Modifications: Ensured state update to `ST_CLOSING` happens before sending the exit message. Result: State update to `ST_CLOSING` happens before sending the exit message, preventing false positive unexpected close detections. resolves #275 --- .../r2dbc/mysql/client/ReactorNettyClient.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java index 961b2806e..8e926f21e 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java @@ -131,6 +131,14 @@ final class ReactorNettyClient implements Client { logger.debug("Request: {}", message); } + if (message == ExitMessage.INSTANCE) { + if (STATE_UPDATER.compareAndSet(this, ST_CONNECTED, ST_CLOSING)) { + logger.debug("Exit message sent"); + } else { + logger.debug("Exit message sent (duplicated / connection already closed)"); + } + } + if (message.isSequenceReset()) { resetSequence(connection); } @@ -213,15 +221,8 @@ public Mono close() { requestQueue.submit(RequestTask.wrap(sink, Mono.fromRunnable(() -> { Sinks.EmitResult result = requests.tryEmitNext(ExitMessage.INSTANCE); - if (result != Sinks.EmitResult.OK) { logger.error("Exit message sending failed due to {}, force closing", result); - } else { - if (STATE_UPDATER.compareAndSet(this, ST_CONNECTED, ST_CLOSING)) { - logger.debug("Exit message sent"); - } else { - logger.debug("Exit message sent (duplicated / connection already closed)"); - } } }))); }).flatMap(Function.identity()).onErrorResume(e -> { From 7f17f9aa5e06272e21418297a42da49924a5bbec Mon Sep 17 00:00:00 2001 From: jchrys Date: Tue, 18 Feb 2025 20:45:09 +0900 Subject: [PATCH 27/42] Upgrade Project Reactor to 2024.0.3 Motivation: The project was using an outdated Project Reactor (2022.0.16). Modifications: Updated Project Reactor to 2024.0.3. Result: Latest Project Reactor applied --- r2dbc-mysql/pom.xml | 4 ++-- test-native-image/pom.xml | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 43d118836..21e6e9356 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -73,8 +73,8 @@ false 1.0.0.RELEASE - 2022.0.16 - 4.1.106.Final + 2024.0.3 + 4.1.118.Final 3.25.3 1.37 5.10.2 diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index 1640175cb..3ea1602d0 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -15,9 +15,9 @@ 8 true - 2022.0.16 + 2024.0.3 1.0.0.RELEASE - 20.3.13 + 20.3.17 @@ -67,7 +67,12 @@ ${skipNativeImage} ${project.artifactId} io.asyncer.Main - --report-unsupported-elements-at-runtime --allow-incomplete-classpath --initialize-at-run-time=io.netty.handler.ssl.BouncyCastleAlpnSslUtils + + --report-unsupported-elements-at-runtime + --allow-incomplete-classpath + --initialize-at-run-time=io.netty.handler.ssl.BouncyCastleAlpnSslUtils + --initialize-at-run-time=io.netty.handler.ssl.JdkSslServerContext + From e211cd89ec4bc9ef034eef00d098d627834b67a3 Mon Sep 17 00:00:00 2001 From: jchrys Date: Tue, 18 Feb 2025 23:31:07 +0900 Subject: [PATCH 28/42] Upgrade Maven to the latest version Motivation: Maven version was outdated. Modifications: Updated Maven to the latest version. Result: Maven is now up to date. --- .mvn/wrapper/maven-wrapper.properties | 7 +- mvnw | 437 ++++++++++++-------------- mvnw.cmd | 306 ++++++++---------- 3 files changed, 323 insertions(+), 427 deletions(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 110dad7d8..d58dfb70b 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -6,7 +6,7 @@ # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # -# https://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -14,5 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar \ No newline at end of file +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/mvnw b/mvnw index 66df28542..19529ddf8 100755 --- a/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # -# https://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -19,290 +19,241 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false +# OS specific support. +native_path() { printf %s\\n "$1"; } case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; esac -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi fi else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi +} - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" + printf %x\\n $h } -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 } -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT else - log "Couldn't find $wrapperJarPath, downloading it ..." + die "cannot create temp dir" +fi - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" +mkdir -p -- "${MAVEN_HOME%/*}" - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -########################################################################################## -# End of extension -########################################################################################## -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 93880f5d9..b150b91ed 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,3 +1,4 @@ +<# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @@ -7,7 +8,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -18,188 +19,131 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir +@REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" From 5425fbb256e6534dc4d156c91cfe4e84024186d9 Mon Sep 17 00:00:00 2001 From: jchrys Date: Wed, 19 Feb 2025 23:22:35 +0900 Subject: [PATCH 29/42] Ensure Reactor-Netty Forward Compatibility Motivation: A change in `r.n.t.SslProvider.ProtocolSslContextSpec` breaks forward compatibility. Modifications: Remove Usage of `ProtocolSslContextSpec`. Result: The SSL context is now correctly initialized under the old reactor-netty version, ensuring forward compatibility. --- .../r2dbc/mysql/client/SslBridgeHandler.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/SslBridgeHandler.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/SslBridgeHandler.java index ce3361fd4..22038bb43 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/SslBridgeHandler.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/SslBridgeHandler.java @@ -33,6 +33,7 @@ import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; +import reactor.core.Exceptions; import reactor.netty.tcp.SslProvider; import javax.net.ssl.HostnameVerifier; @@ -40,7 +41,6 @@ import javax.net.ssl.SSLException; import java.io.File; import java.net.InetSocketAddress; -import java.util.function.Consumer; import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull; import static io.netty.handler.ssl.SslProvider.JDK; @@ -151,10 +151,16 @@ private void handleSslState(ChannelHandlerContext ctx, SslState state) { switch (state) { case BRIDGING: logger.debug("SSL event triggered, enable SSL handler to pipeline"); - - SslProvider sslProvider = SslProvider.builder() - .sslContext(MySqlSslContextSpec.forClient(ssl, context)) - .build(); + final SslProvider sslProvider; + try { + // Workaround for a forward incompatible change in reactor-netty version 1.2.0 + // See: https://github.com/reactor/reactor-netty/commit/6d0c24d83a7c5b15e403475272293f847415191c + sslProvider = SslProvider.builder() + .sslContext(MySqlSslContextSpec.forClient(ssl, context).sslContext()) + .build(); + } catch (SSLException e) { + throw Exceptions.propagate(e); + } SslHandler sslHandler = sslProvider.getSslContext().newHandler(ctx.alloc()); this.sslEngine = sslHandler.engine(); @@ -195,7 +201,7 @@ private static boolean isTls13Enabled(ConnectionContext context) { || (version.isGreaterThanOrEqualTo(MYSQL_5_6_0) && version.isEnterprise()); } - private static final class MySqlSslContextSpec implements SslProvider.ProtocolSslContextSpec { + private static final class MySqlSslContextSpec { private final SslContextBuilder builder; @@ -203,16 +209,6 @@ private MySqlSslContextSpec(SslContextBuilder builder) { this.builder = builder; } - @Override - public MySqlSslContextSpec configure(Consumer customizer) { - requireNonNull(customizer, "customizer must not be null"); - - customizer.accept(builder); - - return this; - } - - @Override public SslContext sslContext() throws SSLException { return builder.build(); } From 33b258aa4f6938bf32b31f6e5abffc3b51186d1e Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 20 Feb 2025 03:45:51 +0900 Subject: [PATCH 30/42] Fix handling of `TINYINT(1) UNSIGNED` when `tinyInt1isBit` is set Motivation: When the `tinyInt1isBit` flag is set, attempting to convert `TINYINT(1) UNSIGNED` to a boolean results in immediate rejection. Modifications: Prevent conversion of `TINYINT(1) UNSIGNED` to boolean when `tinyInt1isBit` is enabled. Result: `TINYINT(1) UNSIGNED` is handled correctly without unnecessary conversion attempts. --- .../mysql/MySqlConnectionConfiguration.java | 6 +++-- .../mysql/MySqlConnectionFactoryProvider.java | 6 +++-- .../r2dbc/mysql/codec/BooleanCodec.java | 4 +++- .../r2dbc/mysql/codec/DefaultCodecs.java | 22 ++++++++++--------- .../mysql/ConnectionIntegrationTest.java | 13 +++++++++++ 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java index 93ba9ce78..3857451a1 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java @@ -1220,8 +1220,10 @@ public Builder metrics(boolean enabled) { /** * Option to whether the driver should interpret MySQL's TINYINT(1) as a BIT type. - * When enabled, TINYINT(1) columns (both SIGNED and UNSIGNED) will be treated as - * BIT. default to {@code true}. + * When enabled, TINYINT(1) columns will be treated as BIT. Defaults to {@code true}. + *

+ * Note: Only signed TINYINT(1) columns can be treated as BIT or Boolean. + * Ref: https://bugs.mysql.com/bug.php?id=100309 * * @param tinyInt1isBit {@code true} to treat TINYINT(1) as BIT * @return this {@link Builder} diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java index bff335123..5905c56ca 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java @@ -332,8 +332,10 @@ public final class MySqlConnectionFactoryProvider implements ConnectionFactoryPr /** * Option to whether the driver should interpret MySQL's TINYINT(1) as a BIT type. - * When enabled, TINYINT(1) columns (both SIGNED and UNSIGNED) will be treated as - * BIT. default to {@code true}. + * When enabled, TINYINT(1) columns will be treated as BIT. Defaults to {@code true}. + *

+ * Note: Only signed TINYINT(1) columns can be treated as BIT or Boolean. + * Ref: https://bugs.mysql.com/bug.php?id=100309 * * @since 1.4.0 */ diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java index 0a59265c3..8fb98c273 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java @@ -32,6 +32,8 @@ */ final class BooleanCodec extends AbstractPrimitiveCodec { + private static final Integer INTEGER_ONE = Integer.valueOf(1); + static final BooleanCodec INSTANCE = new BooleanCodec(); private BooleanCodec() { @@ -86,7 +88,7 @@ public MySqlParameter encode(Object value, CodecContext context) { public boolean doCanDecode(MySqlReadableMetadata metadata) { MySqlType type = metadata.getType(); return ((type == MySqlType.BIT || type == MySqlType.TINYINT) && - Integer.valueOf(1).equals(metadata.getPrecision())) || type == MySqlType.VARCHAR; + INTEGER_ONE.equals(metadata.getPrecision())) || type == MySqlType.VARCHAR; } public Boolean createFromLong(long l) { diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java index ba022875a..34f2c67c1 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java @@ -45,8 +45,6 @@ */ final class DefaultCodecs implements Codecs { - private static final Integer INTEGER_ONE = Integer.valueOf(1); - private static final List> DEFAULT_CODECS = InternalArrays.asImmutableList( ByteCodec.INSTANCE, ShortCodec.INSTANCE, @@ -369,18 +367,22 @@ private static Class chooseClass(final MySqlReadableMetadata metadata, Class< return type.isAssignableFrom(javaType) ? javaType : type; } - private static Class getDefaultJavaType(final MySqlReadableMetadata metadata, final CodecContext codecContext) { - final MySqlType type = metadata.getType(); - final Integer precision = metadata.getPrecision(); - if (INTEGER_ONE.equals(precision) && (type == MySqlType.TINYINT || type == MySqlType.TINYINT_UNSIGNED) - && codecContext.isTinyInt1isBit()) { - return Boolean.class; + private static boolean shouldBeTreatedAsBoolean(final @Nullable Integer precision, final MySqlType type, + final CodecContext context) { + if (precision == null || precision != 1) { + return false; } - // ref: https://github.com/asyncer-io/r2dbc-mysql/issues/277 // BIT(1) should be treated as Boolean by default. - if (INTEGER_ONE.equals(precision) && type == MySqlType.BIT) { + return type == MySqlType.BIT || type == MySqlType.TINYINT && context.isTinyInt1isBit(); + } + + private static Class getDefaultJavaType(final MySqlReadableMetadata metadata, final CodecContext codecContext) { + final MySqlType type = metadata.getType(); + final Integer precision = metadata.getPrecision(); + + if (shouldBeTreatedAsBoolean(precision, type, codecContext)) { return Boolean.class; } diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java index fccd38530..4e4fa34ae 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java @@ -592,6 +592,19 @@ public void tinyInt1isBitTrueTestValue1() { ); } + @Test + public void tinyInt1isBitTrueTestUnsignedTinyInt1isNotBoolean() { + complete(connection -> Mono.from(connection.createStatement("CREATE TEMPORARY TABLE `test` (`id` INT NOT NULL PRIMARY KEY, `value` TINYINT(1) UNSIGNED)").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("INSERT INTO `test` VALUES (1, 1)").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(connection.createStatement("SELECT `value` FROM `test`").execute()) + .flatMap(result -> result.map((row, metadata) -> row.get("value", Object.class))) + .doOnNext(value -> assertThat(value).isInstanceOf(Short.class)) + .doOnNext(value -> assertThat(value).isEqualTo(Short.valueOf((short)1))) + ); + } + @Test public void tinyInt1isBitTrueTestValue0() { complete(connection -> Mono.from(connection.createStatement("CREATE TEMPORARY TABLE `test` (`id` INT NOT NULL PRIMARY KEY, `value` TINYINT(1))").execute()) From cceb8bc3e937842f7958982f23bdb39cdd1cbc48 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Thu, 20 Feb 2025 11:10:28 +0000 Subject: [PATCH 31/42] [maven-release-plugin] prepare release r2dbc-mysql-1.4.0 --- r2dbc-mysql/pom.xml | 4 ++-- test-native-image/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 21e6e9356..90e184254 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.4.0-SNAPSHOT + 1.4.0 Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - HEAD + r2dbc-mysql-1.4.0 diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index 3ea1602d0..e260321f2 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.4.0-SNAPSHOT + 1.4.1-SNAPSHOT UTF-8 From 9633b71b7103fd331cf4e62bb87e881f3388ef76 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Thu, 20 Feb 2025 11:10:31 +0000 Subject: [PATCH 32/42] [maven-release-plugin] prepare for next development iteration --- r2dbc-mysql/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 90e184254..904e49fb5 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.4.0 + 1.4.1-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - r2dbc-mysql-1.4.0 + HEAD From d67bf119d03186a9924d82f6252a278325e14600 Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 20 Feb 2025 20:39:29 +0900 Subject: [PATCH 33/42] Update README Motivation: Released 1.4.0 Modifications: Released 1.4.0 Result: up-to-date --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2a9e75b77..85039510a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Refer to the table below to determine the appropriate version of r2dbc-mysql for | spring-boot-starter-data-r2dbc | spring-data-r2dbc | r2dbc-spi | r2dbc-mysql(recommended) | |--------------------------------|-------------------|---------------|------------------------------| -| 3.0.* and above | 3.0.* and above | 1.0.0.RELEASE | io.asyncer:r2dbc-mysql:1.3.1 | +| 3.0.* and above | 3.0.* and above | 1.0.0.RELEASE | io.asyncer:r2dbc-mysql:1.4.0 | | 2.7.* | 1.5.* | 0.9.1.RELEASE | io.asyncer:r2dbc-mysql:0.9.7 | | 2.6.* and below | 1.4.* and below | 0.8.6.RELEASE | dev.miku:r2dbc-mysql:0.8.2 | @@ -64,7 +64,7 @@ However, Docker-certified images do not have these versions lower than 5.5.0, so io.asyncer r2dbc-mysql - 1.3.1 + 1.4.0 ``` @@ -74,7 +74,7 @@ However, Docker-certified images do not have these versions lower than 5.5.0, so ```groovy dependencies { - implementation 'io.asyncer:r2dbc-mysql:1.3.1' + implementation 'io.asyncer:r2dbc-mysql:1.4.0' } ``` @@ -83,7 +83,7 @@ dependencies { ```kotlin dependencies { // Maybe should to use `compile` instead of `implementation` on the lower version of Gradle. - implementation("io.asyncer:r2dbc-mysql:1.3.1") + implementation("io.asyncer:r2dbc-mysql:1.4.0") } ``` From 3e76ca79e7ff67de2f76f7437bb13991fc5c7b01 Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 20 Feb 2025 21:04:23 +0900 Subject: [PATCH 34/42] Fix Typo (#310) Motiviation: The `MysqlConnectionConfiguration#toString` has a typo: `tinyint1isBit`. Modifications: Corrected the typo to `tinyInt1isBit`. Result: Fixed. --- .../io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java index 3857451a1..39fb91eb6 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java @@ -418,7 +418,7 @@ private String buildCommonToStringPart() { ", passwordPublisher=" + passwordPublisher + ", resolver=" + resolver + ", metrics=" + metrics + - ", tinyint1isBit=" + tinyInt1isBit; + ", tinyInt1isBit=" + tinyInt1isBit; } /** From b90a453a93831d59ca6c0aaf75b52f0871118506 Mon Sep 17 00:00:00 2001 From: jchrys Date: Mon, 24 Mar 2025 00:50:01 +0900 Subject: [PATCH 35/42] fix(logging): prevent excessive MySQL warning logs (#312) Lower MySQL warning log level from INFO to DEBUG to reduce log spam while maintaining debugging visibility. Closes #311 --- .../r2dbc/mysql/client/ReactorNettyClient.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java index 8e926f21e..5054f3631 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/ReactorNettyClient.java @@ -379,17 +379,17 @@ public void error(Throwable e) { @Override public void next(ServerMessage message) { - if (message instanceof WarningMessage) { - int warnings = ((WarningMessage) message).getWarnings(); - if (warnings == 0) { - if (DEBUG_ENABLED) { + if (DEBUG_ENABLED) { + if (message instanceof WarningMessage) { + final int warnings = ((WarningMessage) message).getWarnings(); + if (warnings == 0) { logger.debug("Response: {}", message); + } else { + logger.debug("Response: {}, reports {} warning(s)", message, warnings); } - } else if (INFO_ENABLED) { - logger.info("Response: {}, reports {} warning(s)", message, warnings); + } else { + logger.debug("Response: {}", message); } - } else if (DEBUG_ENABLED) { - logger.debug("Response: {}", message); } responseProcessor.emitNext(message, EmitFailureHandler.FAIL_FAST); From 880226903522dee664ac72f6b0d00a7f72eb424e Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Mon, 14 Apr 2025 13:36:00 +0000 Subject: [PATCH 36/42] [maven-release-plugin] prepare release r2dbc-mysql-1.4.1 --- r2dbc-mysql/pom.xml | 4 ++-- test-native-image/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 904e49fb5..4d4b5f4e1 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.4.1-SNAPSHOT + 1.4.1 Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - HEAD + r2dbc-mysql-1.4.1 diff --git a/test-native-image/pom.xml b/test-native-image/pom.xml index e260321f2..36b13f6b0 100644 --- a/test-native-image/pom.xml +++ b/test-native-image/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.asyncer test-native-image - 1.4.1-SNAPSHOT + 1.4.2-SNAPSHOT UTF-8 From bae7e03a40d95a4f2d2a5b91808bd649475bd988 Mon Sep 17 00:00:00 2001 From: asyncer-io-bot Date: Mon, 14 Apr 2025 13:36:02 +0000 Subject: [PATCH 37/42] [maven-release-plugin] prepare for next development iteration --- r2dbc-mysql/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index 4d4b5f4e1..f931fddff 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -19,7 +19,7 @@ io.asyncer r2dbc-mysql - 1.4.1 + 1.4.2-SNAPSHOT Reactive Relational Database Connectivity - MySQL https://github.com/asyncer-io/r2dbc-mysql @@ -61,7 +61,7 @@ scm:git:git://github.com/asyncer-io/r2dbc-mysql.git scm:git:ssh://git@github.com/asyncer-io/r2dbc-mysql.git https://github.com/asyncer-io/r2dbc-mysql - r2dbc-mysql-1.4.1 + HEAD From d8e8f8ca464a55460f25245dbc73a7148b17cf06 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 18 May 2025 18:45:16 +0900 Subject: [PATCH 38/42] fix(test): Stringify versions to avoid incorrect Docker tag resolution (#318) motivation: Numeric version like 5.0 resolve to '5', which pulls the latest 5.x version. This caused tests to break due to version mismatches. modifications: Stringify versions results: TestContainer now pulls the correct MySQL image, and tests run reliably. --- .github/workflows/ci-integration-tests.yml | 2 +- .github/workflows/ci-mariadb-intergration-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-integration-tests.yml b/.github/workflows/ci-integration-tests.yml index 1ef3cdb0e..3ac7fb159 100644 --- a/.github/workflows/ci-integration-tests.yml +++ b/.github/workflows/ci-integration-tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: mysql-version: - [5.5, 5.6.45, 5.6, 5.7.28, 5.7, 8.0, 8.1, 8.2, 8.3, 8.4, 9.0] + ['5.5', '5.6.45', '5.6', '5.7.28', '5.7', '8.0', '8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2'] name: Integration test with MySQL ${{ matrix.mysql-version }} steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ci-mariadb-intergration-tests.yml b/.github/workflows/ci-mariadb-intergration-tests.yml index 25cf0e498..fcf38e35c 100644 --- a/.github/workflows/ci-mariadb-intergration-tests.yml +++ b/.github/workflows/ci-mariadb-intergration-tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: mariadb-version: - [10.0, 10.1, 10.2.15, 10.2, 10.3.7, 10.3, 10.5.1, 10.5, 10.6, 10.11] + ['10.0', '10.1', '10.2.15', '10.2', '10.3.7', '10.3', '10.5.1', '10.5', '10.6', '10.11'] name: Integration test with MariaDB ${{ matrix.mariadb-version }} steps: - uses: actions/checkout@v3 From 64ad31a7c5813986acca17f473f79d4ad84db0e4 Mon Sep 17 00:00:00 2001 From: jchrys Date: Sun, 18 May 2025 18:59:23 +0900 Subject: [PATCH 39/42] chore(deps): update testcontainer ver (#317) --- r2dbc-mysql/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index f931fddff..a1b77998b 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -82,7 +82,7 @@ 4.11.0 8.3.0 3.3.3 - 1.19.7 + 1.21.0 4.0.3 5.3.32 2.16.1 From aa27a08708c76f69f86b2b46c6c6aa2fc5ce0430 Mon Sep 17 00:00:00 2001 From: jchrys Date: Thu, 12 Jun 2025 02:29:49 +0900 Subject: [PATCH 40/42] chore(deploy): Migrate from OSSRH to Central Publisher Portal (#323) Motivation: The OSSRH will reach EOL on June 30th, 2025. To continue publishing to Maven Central, migration to the Central Publisher Portal is required. Modifications: Migrated the publishing configuration from OSSRH to the Central Publisher Portal. Results: Ensured uinterrupted deployments post-OSSRH EOL. Signed-off-by: jchrys --- .github/workflows/cd-release.yml | 6 +++--- .github/workflows/cd-snapshot.yml | 6 +++--- r2dbc-mysql/pom.xml | 23 ++++++++++++----------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/cd-release.yml b/.github/workflows/cd-release.yml index a57a2e58e..75b75ccde 100644 --- a/.github/workflows/cd-release.yml +++ b/.github/workflows/cd-release.yml @@ -118,9 +118,9 @@ jobs: with: servers: | [{ - "id": "ossrh-staging", - "username": "${{ secrets.OSSRH_USERNAME }}", - "password": "${{ secrets.OSSRH_PASSWORD }}" + "id": "central", + "username": "${{ secrets.CENTRAL_USERNAME }}", + "password": "${{ secrets.CENTRAL_PASSWORD }}" }] - name: Create Local Deploy Directory diff --git a/.github/workflows/cd-snapshot.yml b/.github/workflows/cd-snapshot.yml index a04976b28..8cd96d40c 100644 --- a/.github/workflows/cd-snapshot.yml +++ b/.github/workflows/cd-snapshot.yml @@ -46,9 +46,9 @@ jobs: with: servers: | [{ - "id": "ossrh-snapshots", - "username": "${{ secrets.OSSRH_USERNAME }}", - "password": "${{ secrets.OSSRH_PASSWORD }}" + "id": "central-portal-snapshots", + "username": "${{ secrets.CENTRAL_USERNAME }}", + "password": "${{ secrets.CENTRAL_PASSWORD }}" }] - name: Prepare Internal Dependencies diff --git a/r2dbc-mysql/pom.xml b/r2dbc-mysql/pom.xml index a1b77998b..a903116a8 100644 --- a/r2dbc-mysql/pom.xml +++ b/r2dbc-mysql/pom.xml @@ -448,6 +448,14 @@ + + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 + + central + + @@ -558,16 +566,9 @@ - - - false - - - true - - ossrh-snapshots - Sonatype Nexus Snapshots - https://s01.oss.sonatype.org/content/repositories/snapshots/ - + + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + From 64922945a1aaa1abc58fbeea0fd21c4d5da68ea9 Mon Sep 17 00:00:00 2001 From: dongko Date: Sat, 17 May 2025 16:57:07 +0900 Subject: [PATCH 41/42] Implement ByteArrayInputStreamCodec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed canEncode, doCanDecode conditions - Update Copyright Year - Add tests for ByteArrayInputStreamCodec - Implement ByteArrayInputStreamCodec Signed-off-by: dongKos Signed-off-by: 김동환 --- .../codec/ByteArrayInputStreamCodec.java | 153 ++++++++++++++++++ .../r2dbc/mysql/codec/DefaultCodecs.java | 3 +- .../codec/ByteArrayInputStreamCodecTest.java | 68 ++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java create mode 100644 r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodecTest.java diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java new file mode 100644 index 000000000..3af23a3bc --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java @@ -0,0 +1,153 @@ +/* + * Copyright 2025 asyncer.io projects + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.asyncer.r2dbc.mysql.codec; + +import io.asyncer.r2dbc.mysql.MySqlParameter; +import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; +import io.asyncer.r2dbc.mysql.constant.MySqlType; +import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import reactor.core.publisher.Mono; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static io.asyncer.r2dbc.mysql.internal.util.InternalArrays.EMPTY_BYTES; + +/** + * Codec for {@link InputStream}. + */ +final class ByteArrayInputStreamCodec extends AbstractClassedCodec { + + static final ByteArrayInputStreamCodec INSTANCE = new ByteArrayInputStreamCodec(); + + private ByteArrayInputStreamCodec() { + super(ByteArrayInputStream.class); + } + + @Override + public ByteArrayInputStream decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, + CodecContext context) { + if (!value.isReadable()) { + return new ByteArrayInputStream(EMPTY_BYTES); + } + return new ByteArrayInputStream(value.array()); + } + + @Override + protected boolean doCanDecode(MySqlReadableMetadata metadata) { + return metadata.getType().isBinary(); + } + + @Override + public boolean canEncode(Object value) { + return value instanceof ByteArrayInputStream; + } + + @Override + public MySqlParameter encode(Object value, CodecContext context) { + return new ByteArrayInputStreamMysqlParameter((ByteArrayInputStream) value); + } + + private static final class ByteArrayInputStreamMysqlParameter extends AbstractMySqlParameter { + + private final ByteArrayInputStream value; + + private ByteArrayInputStreamMysqlParameter(ByteArrayInputStream value) { + this.value = value; + } + + @Override + public Mono publishBinary(ByteBufAllocator allocator) { + return Mono.fromSupplier(() -> { + int size = value.available(); + if (size == 0) { + return allocator.buffer(Byte.BYTES).writeByte(0); + } + + int addedSize = VarIntUtils.varIntBytes(size); + ByteBuf buf = allocator.buffer(addedSize + size); + + try { + VarIntUtils.writeVarInt(buf, size); + + byte[] byteArray = new byte[size]; + int readBytes = value.read(byteArray); + + if (readBytes != size) { + buf.release(); + throw new IllegalStateException("Expected to read " + size + " bytes, but got " + readBytes); + } + + return buf.writeBytes(byteArray); + } catch (Exception e) { + buf.release(); + throw new RuntimeException(e); + } + }); + } + + @Override + public Mono publishText(ParameterWriter writer) { + return Mono.fromRunnable(() -> { + try { + int size = value.available(); + byte[] byteArray = new byte[size]; + int readBytes = value.read(byteArray); + + if (size != 0 && readBytes != size) { + throw new IllegalStateException("Expected to read " + size + " bytes, but got " + readBytes); + } + + writer.writeHex(byteArray); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ByteArrayInputStreamMysqlParameter)) { + return false; + } + + ByteArrayInputStreamMysqlParameter that = (ByteArrayInputStreamMysqlParameter) o; + return value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public MySqlType getType() { + return MySqlType.VARBINARY; + } + } +} diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java index 34f2c67c1..01e47348c 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DefaultCodecs.java @@ -81,7 +81,8 @@ final class DefaultCodecs implements Codecs { BlobCodec.INSTANCE, ByteBufferCodec.INSTANCE, - ByteArrayCodec.INSTANCE + ByteArrayCodec.INSTANCE, + ByteArrayInputStreamCodec.INSTANCE ); private final List> codecs; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodecTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodecTest.java new file mode 100644 index 000000000..8c7e8b847 --- /dev/null +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodecTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 asyncer.io projects + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.asyncer.r2dbc.mysql.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.testcontainers.shaded.org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +/** + * Unit tests for {@link ByteArrayInputStreamCodec}. + */ +public class ByteArrayInputStreamCodecTest implements CodecTestSupport { + + private final byte[][] rawData = { + new byte[0], + new byte[] { 0x7F }, + new byte[] { 0x12, 34, 0x56, 78, (byte) 0x9A }, + "Hello world!".getBytes(StandardCharsets.US_ASCII), + new byte[] { (byte) 0xFE, (byte) 0xDC, (byte) 0xBA }, + }; + + private final ByteArrayInputStream[] data = Arrays.stream(rawData) + .map(ByteArrayInputStream::new) + .toArray(ByteArrayInputStream[]::new); + + @Override + public Codec getCodec() { + return ByteArrayInputStreamCodec.INSTANCE; + } + + @Override + public ByteArrayInputStream[] originParameters() { + return data; + } + + @Override + public Object[] stringifyParameters() { + return Arrays.stream(rawData) + .map(bytes -> String.format("x'%s'", Hex.toHexString(bytes))) + .toArray(); + } + + @Override + public ByteBuf[] binaryParameters(Charset charset) { + return Arrays.stream(rawData) + .map(Unpooled::wrappedBuffer) + .toArray(ByteBuf[]::new); + } +} From 8b47ca535fed72c29f9d83ac3feee1221530998d Mon Sep 17 00:00:00 2001 From: jchrys Date: Sat, 28 Jun 2025 22:17:25 +0900 Subject: [PATCH 42/42] =?UTF-8?q?refactor(codec):=20remove=20intermediate?= =?UTF-8?q?=20byte=20array=20in=20ByteArrayInputStre=E2=80=A6=20(#324)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …amCodec Signed-off-by: jchrys --- .../r2dbc/mysql/codec/ByteArrayInputStreamCodec.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java index 3af23a3bc..c31261f5a 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayInputStreamCodec.java @@ -86,16 +86,13 @@ public Mono publishBinary(ByteBufAllocator allocator) { try { VarIntUtils.writeVarInt(buf, size); - - byte[] byteArray = new byte[size]; - int readBytes = value.read(byteArray); - + int readBytes = buf.writeBytes(value, size); if (readBytes != size) { buf.release(); throw new IllegalStateException("Expected to read " + size + " bytes, but got " + readBytes); } - return buf.writeBytes(byteArray); + return buf; } catch (Exception e) { buf.release(); throw new RuntimeException(e);