Skip to content

Commit c018cc5

Browse files
committed
Add support for MariaDB version pattern
1 parent 96c11dc commit c018cc5

File tree

12 files changed

+189
-83
lines changed

12 files changed

+189
-83
lines changed

src/main/java/io/asyncer/r2dbc/mysql/Capability.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ public final class Capability {
175175

176176
private final long bitmap;
177177

178+
/**
179+
* Checks if the connection is using MariaDB capabilities.
180+
*
181+
* @return if using MariaDB capabilities.
182+
*/
178183
public boolean isMariaDb() {
179184
return (bitmap & CLIENT_MYSQL) == 0;
180185
}

src/main/java/io/asyncer/r2dbc/mysql/ConnectionContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ public ZoneId getServerZoneId() {
9898
return serverZoneId;
9999
}
100100

101+
@Override
102+
public boolean isMariaDb() {
103+
return capability.isMariaDb() || serverVersion.isMariaDb();
104+
}
105+
101106
boolean shouldSetServerZoneId() {
102107
return serverZoneId == null;
103108
}

src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnDescriptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public Integer getScale() {
122122
@Override
123123
public CharCollation getCharCollation(CodecContext context) {
124124
return collationId == CharCollation.BINARY_ID ? context.getClientCollation() :
125-
CharCollation.fromId(collationId, context.getServerVersion());
125+
CharCollation.fromId(collationId, context);
126126
}
127127

128128
@Override

src/main/java/io/asyncer/r2dbc/mysql/MySqlConnection.java

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,13 @@ public final class MySqlConnection implements Connection, Lifecycle, ConnectionS
6666

6767
private static final int PREFIX_LENGTH = 6;
6868

69-
/**
70-
* If MySQL server version greater than or equal to {@literal 8.0.3}, or greater than or equal to
71-
* {@literal 5.7.20} and less than {@literal 8.0.0}, the column name of current session isolation level
72-
* will be {@literal @@transaction_isolation}, otherwise it is {@literal @@tx_isolation}.
73-
*
74-
* @see #init judge server version before get the isolation level.
75-
*/
76-
private static final ServerVersion TRAN_LEVEL_8X = ServerVersion.create(8, 0, 3);
69+
private static final ServerVersion MARIA_11_1_1 = ServerVersion.create(11, 1, 1, true);
7770

78-
private static final ServerVersion TRAN_LEVEL_5X = ServerVersion.create(5, 7, 20);
71+
private static final ServerVersion MYSQL_8_0_3 = ServerVersion.create(8, 0, 3);
7972

80-
private static final ServerVersion TX_LEVEL_8X = ServerVersion.create(8, 0, 0);
73+
private static final ServerVersion MYSQL_5_7_20 = ServerVersion.create(5, 7, 20);
74+
75+
private static final ServerVersion MYSQL_8 = ServerVersion.create(8, 0, 0);
8176

8277
private static final BiConsumer<ServerMessage, SynchronousSink<Boolean>> PING = (message, sink) -> {
8378
if (message instanceof ErrorMessage) {
@@ -427,17 +422,10 @@ static Mono<MySqlConnection> init(
427422
QueryCache queryCache, PrepareCache prepareCache,
428423
@Nullable Predicate<String> prepare
429424
) {
430-
ServerVersion version = context.getServerVersion();
431-
StringBuilder query = new StringBuilder(128);
432-
433-
// Maybe create a InitFlow for data initialization after login?
434-
if (version.isGreaterThanOrEqualTo(TRAN_LEVEL_8X) ||
435-
(version.isGreaterThanOrEqualTo(TRAN_LEVEL_5X) && version.isLessThan(TX_LEVEL_8X))) {
436-
query.append(
437-
"SELECT @@transaction_isolation AS i,@@innodb_lock_wait_timeout AS l,@@version_comment AS v");
438-
} else {
439-
query.append("SELECT @@tx_isolation AS i,@@innodb_lock_wait_timeout AS l,@@version_comment AS v");
440-
}
425+
StringBuilder query = new StringBuilder(128)
426+
.append("SELECT ")
427+
.append(transactionIsolationColumn(context))
428+
.append(",@@innodb_lock_wait_timeout AS l,@@version_comment AS v");
441429

442430
Function<MySqlResult, Publisher<InitData>> handler;
443431

@@ -587,6 +575,28 @@ private static long convertLockWaitTimeout(@Nullable Long timeout) {
587575
return timeout;
588576
}
589577

578+
/**
579+
* Resolves the column of session isolation level, the {@literal @@tx_isolation} has been marked as
580+
* deprecated.
581+
* <p>
582+
* If server is MariaDB, {@literal @@transaction_isolation} is used starting from {@literal 11.1.1}.
583+
* <p>
584+
* If the server is MySQL, use {@literal @@transaction_isolation} starting from {@literal 8.0.3}, or
585+
* between {@literal 5.7.20} and {@literal 8.0.0} (exclusive).
586+
*/
587+
private static String transactionIsolationColumn(ConnectionContext context) {
588+
ServerVersion version = context.getServerVersion();
589+
590+
if (context.isMariaDb()) {
591+
return version.isGreaterThanOrEqualTo(MARIA_11_1_1) ? "@@transaction_isolation AS i" :
592+
"@@tx_isolation AS i";
593+
}
594+
595+
return version.isGreaterThanOrEqualTo(MYSQL_8_0_3) ||
596+
(version.isGreaterThanOrEqualTo(MYSQL_5_7_20) && version.isLessThan(MYSQL_8)) ?
597+
"@@transaction_isolation AS i" : "@@tx_isolation AS i";
598+
}
599+
590600
private static class InitData {
591601

592602
private final IsolationLevel level;

src/main/java/io/asyncer/r2dbc/mysql/ServerVersion.java

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
*/
2525
public final class ServerVersion implements Comparable<ServerVersion> {
2626

27+
/**
28+
* MariaDB's replication hack prefix.
29+
* <p>
30+
* Note: MySQL 5.5.5 is not a stable version, so it should be safe.
31+
*/
32+
private static final String MARIADB_RPL_HACK_PREFIX = "5.5.5-";
33+
2734
private static final String ENTERPRISE = "enterprise";
2835

2936
private static final String COMMERCIAL = "commercial";
@@ -42,11 +49,14 @@ public final class ServerVersion implements Comparable<ServerVersion> {
4249

4350
private final int patch;
4451

45-
private ServerVersion(String origin, int major, int minor, int patch) {
52+
private final boolean isMariaDb;
53+
54+
private ServerVersion(String origin, int major, int minor, int patch, boolean isMariaDb) {
4655
this.origin = origin;
4756
this.major = major;
4857
this.minor = minor;
4958
this.patch = patch;
59+
this.isMariaDb = isMariaDb;
5060
}
5161

5262
/**
@@ -97,6 +107,15 @@ public int getPatch() {
97107
return patch;
98108
}
99109

110+
/**
111+
* Checks {@link ServerVersion this} contains MariaDB prefix or postfix.
112+
*
113+
* @return if it contains.
114+
*/
115+
public boolean isMariaDb() {
116+
return isMariaDb;
117+
}
118+
100119
/**
101120
* Checks if the version is enterprise edition.
102121
* <p>
@@ -150,32 +169,39 @@ public static ServerVersion parse(String version) {
150169

151170
int length = version.length();
152171
int[] index = new int[] { 0 };
153-
int major = readInt(version, length, index);
154-
155-
if (index[0] >= length) {
156-
// End-of-string.
157-
return create0(version, major, 0, 0);
158-
} else if (version.charAt(index[0]) != '.') {
159-
// Is not '.', has only postfix after major.
160-
return create0(version, major, 0, 0);
161-
} else {
162-
// Skip last '.' after major.
163-
++index[0];
172+
boolean isMariaDb = false;
173+
174+
if (version.startsWith(MARIADB_RPL_HACK_PREFIX)) {
175+
isMariaDb = true;
176+
index[0] = MARIADB_RPL_HACK_PREFIX.length();
164177
}
165178

166-
int minor = readInt(version, length, index);
179+
int[] parts = new int[] { 0, 0, 0 };
180+
int i = 0;
181+
182+
while (true) {
183+
parts[i] = readInt(version, length, index);
167184

168-
if (index[0] >= length) {
169-
return create0(version, major, minor, 0);
170-
} else if (version.charAt(index[0]) != '.') {
171-
// Is not '.', has only postfix after minor.
172-
return create0(version, major, minor, 0);
173-
} else {
174-
// Skip last '.' after minor.
185+
if (index[0] >= length) {
186+
// End of version.
187+
break;
188+
}
189+
190+
if (i == 2 || version.charAt(index[0]) != '.') {
191+
// End of version number parts, check postfix if needed.
192+
if (!isMariaDb) {
193+
isMariaDb = version.indexOf("MariaDB", index[0]) >= 0;
194+
}
195+
196+
break;
197+
}
198+
199+
// Skip last '.' after current number part.
175200
++index[0];
201+
++i;
176202
}
177203

178-
return create0(version, major, minor, readInt(version, length, index));
204+
return create0(version, parts[0], parts[1], parts[2], isMariaDb);
179205
}
180206

181207
/**
@@ -188,15 +214,29 @@ public static ServerVersion parse(String version) {
188214
* @throws IllegalArgumentException if any version part is negative integer.
189215
*/
190216
public static ServerVersion create(int major, int minor, int patch) {
191-
return create0("", major, minor, patch);
217+
return create0("", major, minor, patch, false);
218+
}
219+
220+
/**
221+
* Create a {@link ServerVersion} that value is {@literal major.minor.patch} with MariaDB flag.
222+
*
223+
* @param major must not be a negative integer
224+
* @param minor must not be a negative integer
225+
* @param patch must not be a negative integer
226+
* @param isMariaDb MariaDB flag
227+
* @return A server version that value is {@literal major.minor.patch}
228+
* @throws IllegalArgumentException if any version part is negative integer.
229+
*/
230+
public static ServerVersion create(int major, int minor, int patch, boolean isMariaDb) {
231+
return create0("", major, minor, patch, isMariaDb);
192232
}
193233

194-
private static ServerVersion create0(String origin, int major, int minor, int patch) {
234+
private static ServerVersion create0(String origin, int major, int minor, int patch, boolean isMariaDb) {
195235
require(major >= 0, "major version must not be a negative integer");
196236
require(minor >= 0, "minor version must not be a negative integer");
197237
require(patch >= 0, "patch version must not be a negative integer");
198238

199-
return new ServerVersion(origin, major, minor, patch);
239+
return new ServerVersion(origin, major, minor, patch, isMariaDb);
200240
}
201241

202242
/**

src/main/java/io/asyncer/r2dbc/mysql/client/SslBridgeHandler.java

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,19 @@ final class SslBridgeHandler extends ChannelDuplexHandler {
6363

6464
private static final String[] OLD_TLS_PROTOCOLS = new String[] { TlsVersions.TLS1_1, TlsVersions.TLS1 };
6565

66-
private static final ServerVersion VER_5_6_0 = ServerVersion.create(5, 6, 0);
66+
private static final ServerVersion MARIA_10_2_16 = ServerVersion.create(10, 2, 16, true);
6767

68-
private static final ServerVersion VER_5_6_46 = ServerVersion.create(5, 6, 46);
68+
private static final ServerVersion MARIA_10_3_0 = ServerVersion.create(10, 3, 0, true);
6969

70-
private static final ServerVersion VER_5_7_0 = ServerVersion.create(5, 7, 0);
70+
private static final ServerVersion MARIA_10_3_8 = ServerVersion.create(10, 3, 8, true);
7171

72-
private static final ServerVersion VER_5_7_28 = ServerVersion.create(5, 7, 28);
72+
private static final ServerVersion MYSQL_5_6_0 = ServerVersion.create(5, 6, 0);
73+
74+
private static final ServerVersion MYSQL_5_6_46 = ServerVersion.create(5, 6, 46);
75+
76+
private static final ServerVersion MYSQL_5_7_0 = ServerVersion.create(5, 7, 0);
77+
78+
private static final ServerVersion MYSQL_5_7_28 = ServerVersion.create(5, 7, 28);
7379

7480
private final ConnectionContext context;
7581

@@ -144,7 +150,7 @@ private void handleSslState(ChannelHandlerContext ctx, SslState state) {
144150
logger.debug("SSL event triggered, enable SSL handler to pipeline");
145151

146152
SslProvider sslProvider = SslProvider.builder()
147-
.sslContext(MySqlSslContextSpec.forClient(ssl, context.getServerVersion()))
153+
.sslContext(MySqlSslContextSpec.forClient(ssl, context))
148154
.build();
149155
SslHandler sslHandler = sslProvider.getSslContext().newHandler(ctx.alloc());
150156

@@ -167,13 +173,23 @@ private HostnameVerifier hostnameVerifier() {
167173
return verifier == null ? DefaultHostnameVerifier.INSTANCE : verifier;
168174
}
169175

170-
private static boolean isCurrentTlsEnabled(ServerVersion version) {
171-
// See also https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html
176+
private static boolean isTls13Enabled(ConnectionContext context) {
177+
ServerVersion version = context.getServerVersion();
178+
179+
if (context.isMariaDb()) {
180+
// See also https://mariadb.com/kb/en/secure-connections-overview/#tls-protocol-version-support
181+
// Quoting fragment: MariaDB binaries built with the OpenSSL library (OpenSSL 1.1.1 or later)
182+
// support TLSv1.3 since MariaDB 10.2.16 and MariaDB 10.3.8.
183+
return (version.isGreaterThanOrEqualTo(MARIA_10_2_16) && version.isLessThan(MARIA_10_3_0))
184+
|| version.isGreaterThanOrEqualTo(MARIA_10_3_8);
185+
}
186+
187+
// See also https://dev.mysql.com/doc/relnotes/connector-j/en/news-8-0-19.html
172188
// Quoting fragment: TLSv1,TLSv1.1,TLSv1.2,TLSv1.3 for MySQL Community Servers 8.0, 5.7.28 and
173189
// later, and 5.6.46 and later, and for all commercial versions of MySQL Servers.
174-
return version.isGreaterThanOrEqualTo(VER_5_7_28)
175-
|| (version.isGreaterThanOrEqualTo(VER_5_6_46) && version.isLessThan(VER_5_7_0))
176-
|| (version.isGreaterThanOrEqualTo(VER_5_6_0) && version.isEnterprise());
190+
return version.isGreaterThanOrEqualTo(MYSQL_5_7_28)
191+
|| (version.isGreaterThanOrEqualTo(MYSQL_5_6_46) && version.isLessThan(MYSQL_5_7_0))
192+
|| (version.isGreaterThanOrEqualTo(MYSQL_5_6_0) && version.isEnterprise());
177193
}
178194

179195
private static final class MySqlSslContextSpec implements SslProvider.ProtocolSslContextSpec {
@@ -196,7 +212,7 @@ public SslContext sslContext() throws SSLException {
196212
return builder.build();
197213
}
198214

199-
static MySqlSslContextSpec forClient(MySqlSslConfiguration ssl, ServerVersion version) {
215+
static MySqlSslContextSpec forClient(MySqlSslConfiguration ssl, ConnectionContext context) {
200216
// Same default configuration as TcpSslContextSpec.
201217
SslContextBuilder builder = SslContextBuilder.forClient()
202218
.sslProvider(OpenSsl.isAvailable() ? OPENSSL : JDK)
@@ -206,11 +222,16 @@ static MySqlSslContextSpec forClient(MySqlSslConfiguration ssl, ServerVersion ve
206222

207223
if (tlsProtocols.length > 0) {
208224
builder.protocols(tlsProtocols);
209-
} else if (isCurrentTlsEnabled(version)) {
225+
} else if (isTls13Enabled(context)) {
210226
builder.protocols(TLS_PROTOCOLS);
211227
} else {
212228
// Not sure if we need to check the JDK version, suggest not.
213-
logger.warn("MySQL {} does not support TLS1.2, TLS1.1 is disabled in latest JDKs", version);
229+
if (logger.isWarnEnabled()) {
230+
logger.warn("{} {} does not support TLS1.2, TLS1.1 is disabled in latest JDKs",
231+
context.isMariaDb() ? "MariaDB" : "MySQL",
232+
context.getServerVersion());
233+
}
234+
214235
builder.protocols(OLD_TLS_PROTOCOLS);
215236
}
216237

src/main/java/io/asyncer/r2dbc/mysql/codec/CodecContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,11 @@ public interface CodecContext {
5454
* @return the {@link CharCollation}.
5555
*/
5656
CharCollation getClientCollation();
57+
58+
/**
59+
* Checks server is MariaDB or not.
60+
*
61+
* @return if is MariaDB.
62+
*/
63+
boolean isMariaDb();
5764
}

src/main/java/io/asyncer/r2dbc/mysql/collation/CharCollation.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package io.asyncer.r2dbc.mysql.collation;
1818

19-
import io.asyncer.r2dbc.mysql.ServerVersion;
19+
import io.asyncer.r2dbc.mysql.codec.CodecContext;
2020

2121
import java.nio.charset.Charset;
2222

@@ -62,17 +62,17 @@ public interface CharCollation {
6262

6363
/**
6464
* Obtain an instance of {@link CharCollation} from the identifier and server version, if not found, it
65-
* will fallback to UTF-8. (i.e. utf8mb4)
65+
* will fall back to UTF-8. (i.e. utf8mb4)
6666
*
6767
* @param id character collation identifier.
68-
* @param version the version of MySQL server.
68+
* @param context the codec context of server.
6969
* @return the {@link CharCollation}.
7070
* @throws IllegalArgumentException if {@code version} is {@code null}.
7171
*/
72-
static CharCollation fromId(int id, ServerVersion version) {
73-
requireNonNull(version, "version must not be null");
72+
static CharCollation fromId(int id, CodecContext context) {
73+
requireNonNull(context, "version must not be null");
7474

75-
return CharCollations.fromId(id, version);
75+
return CharCollations.fromId(id, context);
7676
}
7777

7878
/**

0 commit comments

Comments
 (0)