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 cc5aeb2a..81dc01a4 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;
}
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 8b4c789d..fbeb9506 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
+ * {@link Boolean} by default.
+ *
+ * @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 e483d2d6..a6880cc8 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 8f045fca..ef4925e2 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,17 @@ public final class MySqlConnectionFactoryProvider implements ConnectionFactoryPr
*/
public static final Option METRICS = Option.valueOf("metrics");
+ /**
+ * Since the MySQL server silently converts BIT to TINYINT(1) when creating tables,
+ * should the driver treat the datatype TINYINT(1) as the BIT type?
+ *
+ * Note: If {@code tinyInt1isBit=true}, TINYINT(1) columns, whether SIGNED or UNSIGNED,
+ * will be represented as {@link Boolean} by default.
+ *
+ * @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 +435,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 8eda9c98..d535a86d 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,11 @@ 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 4542a7c9..0d3cbc17 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(metadata.getPrecision()) && type == MySqlType.BIT) {
return Boolean.class;
}
+
return type.getJavaType();
}