diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ColumnDefinition.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ColumnDefinition.java
deleted file mode 100644
index 66422f293..000000000
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ColumnDefinition.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2023 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 io.asyncer.r2dbc.mysql.collation.CharCollation;
-
-/**
- * A flag bitmap considers column definitions.
- */
-public final class ColumnDefinition {
-
- private static final short NOT_NULL = 1;
-
-// public static final short PRIMARY_PART = 1 << 1; // This field is a part of the primary key
-// public static final short UNIQUE_PART = 1 << 2; // This field is a part of a unique key
-// public static final short KEY_PART = 1 << 3; // This field is a part of a normal key
-// public static final short BLOB = 1 << 4;
-
- private static final short UNSIGNED = 1 << 5;
-
-// public static final short ZEROFILL = 1 << 6;
-
- public static final short BINARY = 1 << 7;
-
- private static final short ENUM = 1 << 8;
-
-// public static final short AUTO_INCREMENT = 1 << 9;
-// public static final short TIMESTAMP = 1 << 10;
-
- private static final short SET = 1 << 11; // type is set
-
-// public static final short NO_DEFAULT = 1 << 12; // column has no default value
-// public static final short ON_UPDATE_NOW = 1 << 13; // field will be set to NOW() in UPDATE statement
-
- private static final short ALL_USED = NOT_NULL | UNSIGNED | BINARY | ENUM | SET;
-
- /**
- * The original bitmap of {@link ColumnDefinition this}.
- *
- * MySQL uses 32-bits definition flags, but only returns the lower 16-bits.
- */
- private final short bitmap;
-
- /**
- * collation id(or charset number)
- *
- * collationId > 0 when protocol version == 4.1, 0 otherwise.
- */
- private final int collationId;
-
- private ColumnDefinition(short bitmap, int collationId) {
- this.bitmap = bitmap;
- this.collationId = collationId;
- }
-
- /**
- * Checks if value is not null.
- *
- * @return if value is not null.
- */
- public boolean isNotNull() {
- return (bitmap & NOT_NULL) != 0;
- }
-
- /**
- * Checks if value is an unsigned number. e.g. INT UNSIGNED, BIGINT UNSIGNED.
- *
- * Note: IEEE-754 floating types (e.g. DOUBLE/FLOAT) do not support it in MySQL 8.0+. When creating a
- * column as an unsigned floating type, the server may report a warning.
- *
- * @return if value is an unsigned number.
- */
- public boolean isUnsigned() {
- return (bitmap & UNSIGNED) != 0;
- }
-
- /**
- * Checks if value is binary data.
- *
- * @return if value is binary data.
- */
- public boolean isBinary() {
- // Utilize collationId to ascertain whether it is binary or not.
- // This is necessary since the union of JSON columns, varchar binary, and char binary
- // results in a bitmap with the BINARY flag set.
- // see: https://github.com/asyncer-io/r2dbc-mysql/issues/91
- return collationId == 0 & (bitmap & BINARY) != 0 | collationId == CharCollation.BINARY_ID;
- }
-
- /**
- * Checks if value type is enum.
- *
- * @return if value is an enum.
- */
- public boolean isEnum() {
- return (bitmap & ENUM) != 0;
- }
-
- /**
- * Checks if value type is set.
- *
- * @return if value is a set.
- */
- public boolean isSet() {
- return (bitmap & SET) != 0;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof ColumnDefinition)) {
- return false;
- }
-
- ColumnDefinition that = (ColumnDefinition) o;
-
- return bitmap == that.bitmap & collationId == that.collationId;
- }
-
- @Override
- public int hashCode() {
- return bitmap;
- }
-
- @Override
- public String toString() {
- return "ColumnDefinition<0x" + Integer.toHexString(bitmap) + ", 0x" + Integer.toHexString(collationId) + '>';
- }
-
- /**
- * Creates a {@link ColumnDefinition} with column definitions bitmap. It will unset all unknown or useless
- * flags.
- *
- * @param definitions the column definitions bitmap.
- * @return the {@link ColumnDefinition} without unknown or useless flags.
- */
- public static ColumnDefinition of(int definitions) {
- return new ColumnDefinition((short) (definitions & ALL_USED), 0);
- }
-
- /**
- * Creates a {@link ColumnDefinition} with column definitions bitmap. It will unset all unknown or useless
- * flags.
- *
- * @param definitions the column definitions bitmap.
- * @param collationId the collation id.
- * @return the {@link ColumnDefinition} without unknown or useless flags.
- */
- public static ColumnDefinition of(int definitions, int collationId) {
- return new ColumnDefinition((short) (definitions & ALL_USED), collationId);
- }
-}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionState.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionState.java
index 33f8cf551..73a9caf09 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionState.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConnectionState.java
@@ -31,7 +31,7 @@ interface ConnectionState {
void setIsolationLevel(IsolationLevel level);
/**
- * Reutrns session lock wait timeout.
+ * Returns session lock wait timeout.
*
* @return Session lock wait timeout.
*/
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConsistentSnapshotEngine.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConsistentSnapshotEngine.java
index 716b65a37..22131d2ad 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConsistentSnapshotEngine.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ConsistentSnapshotEngine.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 asyncer.io projects
+ * Copyright 2024 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.
@@ -19,7 +19,12 @@
/**
* The engine of {@code START TRANSACTION WITH CONSISTENT [engine] SNAPSHOT} for Facebook/MySQL or similar
* syntax.
+ *
+ * @deprecated use directly {@link String} instead, e.g. {@code "ROCKSDB"}
+ * @see io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition#consistent(String)
+ * @see io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition#consistent(String, long)
*/
+@Deprecated
public enum ConsistentSnapshotEngine {
ROCKSDB,
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/InsertSyntheticRow.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/InsertSyntheticRow.java
index eab13e88c..ec42ae977 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/InsertSyntheticRow.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/InsertSyntheticRow.java
@@ -16,7 +16,13 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlColumnMetadata;
+import io.asyncer.r2dbc.mysql.api.MySqlRow;
+import io.asyncer.r2dbc.mysql.api.MySqlRowMetadata;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
+import io.asyncer.r2dbc.mysql.codec.CodecContext;
import io.asyncer.r2dbc.mysql.codec.Codecs;
+import io.asyncer.r2dbc.mysql.collation.CharCollation;
import io.asyncer.r2dbc.mysql.constant.MySqlType;
import io.r2dbc.spi.ColumnMetadata;
import io.r2dbc.spi.Nullability;
@@ -37,7 +43,7 @@
*
* @see MySqlStatement#returnGeneratedValues(String...) reading last inserted ID.
*/
-final class InsertSyntheticRow implements Row, RowMetadata, ColumnMetadata {
+final class InsertSyntheticRow implements MySqlRow, MySqlRowMetadata, MySqlColumnMetadata {
private final Codecs codecs;
@@ -96,19 +102,19 @@ public boolean contains(String name) {
}
@Override
- public RowMetadata getMetadata() {
+ public MySqlRowMetadata getMetadata() {
return this;
}
@Override
- public ColumnMetadata getColumnMetadata(int index) {
+ public MySqlColumnMetadata getColumnMetadata(int index) {
assertValidIndex(index);
return this;
}
@Override
- public ColumnMetadata getColumnMetadata(String name) {
+ public MySqlColumnMetadata getColumnMetadata(String name) {
requireNonNull(name, "name must not be null");
assertValidName(name);
@@ -116,7 +122,7 @@ public ColumnMetadata getColumnMetadata(String name) {
}
@Override
- public List getColumnMetadatas() {
+ public List getColumnMetadatas() {
return Collections.singletonList(this);
}
@@ -125,6 +131,11 @@ public MySqlType getType() {
return lastInsertId < 0 ? MySqlType.BIGINT_UNSIGNED : MySqlType.BIGINT;
}
+ @Override
+ public CharCollation getCharCollation(CodecContext context) {
+ return context.getClientCollation();
+ }
+
@Override
public String getName() {
return keyName;
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatchingBatch.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatchingBatch.java
index 6d74cf4d0..d85ebfd9e 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatchingBatch.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatchingBatch.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlBatch;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import reactor.core.publisher.Flux;
@@ -28,7 +30,7 @@
* An implementation of {@link MySqlBatch} for executing a collection of statements in a batch against the
* MySQL database.
*/
-final class MySqlBatchingBatch extends MySqlBatch {
+final class MySqlBatchingBatch implements MySqlBatch {
private final Client client;
@@ -63,7 +65,7 @@ public MySqlBatch add(String sql) {
@Override
public Flux execute() {
return QueryFlow.execute(client, getSql())
- .map(messages -> MySqlResult.toResult(false, codecs, context, null, messages));
+ .map(messages -> MySqlSegmentResult.toResult(false, codecs, context, null, messages));
}
@Override
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnDescriptor.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnDescriptor.java
index 4bc9aaca4..5f3720c08 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnDescriptor.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnDescriptor.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlColumnMetadata;
+import io.asyncer.r2dbc.mysql.api.MySqlNativeTypeMetadata;
import io.asyncer.r2dbc.mysql.codec.CodecContext;
import io.asyncer.r2dbc.mysql.collation.CharCollation;
import io.asyncer.r2dbc.mysql.constant.MySqlType;
@@ -48,28 +50,28 @@ final class MySqlColumnDescriptor implements MySqlColumnMetadata {
private final int collationId;
- private MySqlColumnDescriptor(int index, short typeId, String name, ColumnDefinition definition,
+ private MySqlColumnDescriptor(int index, short typeId, String name, int definitions,
long size, int decimals, int collationId) {
require(index >= 0, "index must not be a negative integer");
require(size >= 0, "size must not be a negative integer");
require(decimals >= 0, "decimals must not be a negative integer");
requireNonNull(name, "name must not be null");
- require(collationId > 0, "collationId must be a positive integer");
- requireNonNull(definition, "definition must not be null");
+
+ MySqlTypeMetadata typeMetadata = new MySqlTypeMetadata(typeId, definitions, collationId);
this.index = index;
- this.typeMetadata = new MySqlTypeMetadata(typeId, definition);
- this.type = MySqlType.of(typeId, definition);
+ this.typeMetadata = typeMetadata;
+ this.type = MySqlType.of(typeMetadata);
this.name = name;
- this.nullability = definition.isNotNull() ? Nullability.NON_NULL : Nullability.NULLABLE;
+ this.nullability = typeMetadata.isNotNull() ? Nullability.NON_NULL : Nullability.NULLABLE;
this.size = size;
this.decimals = decimals;
this.collationId = collationId;
}
static MySqlColumnDescriptor create(int index, DefinitionMetadataMessage message) {
- ColumnDefinition definition = message.getDefinition();
- return new MySqlColumnDescriptor(index, message.getTypeId(), message.getColumn(), definition,
+ int definitions = message.getDefinitions();
+ return new MySqlColumnDescriptor(index, message.getTypeId(), message.getColumn(), definitions,
message.getSize(), message.getDecimals(), message.getCollationId());
}
@@ -88,7 +90,7 @@ public String getName() {
}
@Override
- public MySqlTypeMetadata getNativeTypeMetadata() {
+ public MySqlNativeTypeMetadata getNativeTypeMetadata() {
return typeMetadata;
}
@@ -99,14 +101,13 @@ public Nullability getNullability() {
@Override
public Integer getPrecision() {
+ // FIXME: NEW_DECIMAL and DECIMAL are "exact" fixed-point number.
+ // So the `size` have to subtract:
+ // 1. if signed, 1 byte for the sign
+ // 2. if decimals > 0, 1 byte for the dot
return (int) size;
}
- @Override
- public long getNativePrecision() {
- return size;
- }
-
@Override
public Integer getScale() {
// 0x00 means it is an integer or a static string.
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 ec2d57339..9e269eda5 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
@@ -16,6 +16,7 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlConnection;
import io.asyncer.r2dbc.mysql.cache.Caches;
import io.asyncer.r2dbc.mysql.cache.PrepareCache;
import io.asyncer.r2dbc.mysql.cache.QueryCache;
@@ -52,14 +53,14 @@
*/
public final class MySqlConnectionFactory implements ConnectionFactory {
- private final Mono client;
+ private final Mono extends MySqlConnection> client;
- private MySqlConnectionFactory(Mono client) {
+ private MySqlConnectionFactory(Mono extends MySqlConnection> client) {
this.client = client;
}
@Override
- public Mono create() {
+ public Mono extends MySqlConnection> create() {
return client;
}
@@ -174,7 +175,7 @@ private static Mono getMySqlConnection(
extensions.forEach(CodecRegistrar.class, registrar ->
registrar.register(allocator, builder));
- return MySqlConnection.init(client, builder.build(), context, db, queryCache.get(),
+ return MySqlSimpleConnection.init(client, builder.build(), context, db, queryCache.get(),
prepareCache, sessionVariables, prepare);
});
}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRow.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlDataRow.java
similarity index 91%
rename from r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRow.java
rename to r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlDataRow.java
index 04cc12eff..11575244d 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRow.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlDataRow.java
@@ -16,10 +16,11 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlRow;
+import io.asyncer.r2dbc.mysql.api.MySqlRowMetadata;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import io.asyncer.r2dbc.mysql.message.FieldValue;
import io.r2dbc.spi.Row;
-import io.r2dbc.spi.RowMetadata;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.ParameterizedType;
@@ -29,11 +30,11 @@
/**
* An implementation of {@link Row} for MySQL database.
*/
-public final class MySqlRow implements Row {
+final class MySqlDataRow implements MySqlRow {
private final FieldValue[] fields;
- private final MySqlRowMetadata rowMetadata;
+ private final MySqlRowDescriptor rowMetadata;
private final Codecs codecs;
@@ -44,7 +45,7 @@ public final class MySqlRow implements Row {
private final ConnectionContext context;
- MySqlRow(FieldValue[] fields, MySqlRowMetadata rowMetadata, Codecs codecs, boolean binary,
+ MySqlDataRow(FieldValue[] fields, MySqlRowDescriptor rowMetadata, Codecs codecs, boolean binary,
ConnectionContext context) {
this.fields = requireNonNull(fields, "fields must not be null");
this.rowMetadata = requireNonNull(rowMetadata, "rowMetadata must not be null");
@@ -107,7 +108,7 @@ public T get(String name, ParameterizedType type) {
* {@inheritDoc}
*/
@Override
- public RowMetadata getMetadata() {
+ public MySqlRowMetadata getMetadata() {
return rowMetadata;
}
}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRowMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRowDescriptor.java
similarity index 88%
rename from r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRowMetadata.java
rename to r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRowDescriptor.java
index 37f26ac7b..1b5311da6 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRowMetadata.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlRowDescriptor.java
@@ -16,9 +16,9 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlRowMetadata;
import io.asyncer.r2dbc.mysql.internal.util.InternalArrays;
import io.asyncer.r2dbc.mysql.message.server.DefinitionMetadataMessage;
-import io.r2dbc.spi.RowMetadata;
import java.util.Arrays;
import java.util.List;
@@ -27,11 +27,11 @@
import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull;
/**
- * An implementation of {@link RowMetadata} for MySQL database text/binary results.
+ * An implementation of {@link MySqlRowMetadata} for MySQL database text/binary results.
*
* @see MySqlNames column name searching rules.
*/
-final class MySqlRowMetadata implements RowMetadata {
+final class MySqlRowDescriptor implements MySqlRowMetadata {
private final MySqlColumnDescriptor[] originMetadata;
@@ -39,7 +39,7 @@ final class MySqlRowMetadata implements RowMetadata {
private final ColumnNameSet nameSet;
- private MySqlRowMetadata(MySqlColumnDescriptor[] metadata) {
+ private MySqlRowDescriptor(MySqlColumnDescriptor[] metadata) {
int size = metadata.length;
switch (size) {
@@ -105,7 +105,7 @@ public List getColumnMetadatas() {
@Override
public String toString() {
- return "MySqlRowMetadata{metadata=" + Arrays.toString(originMetadata) + ", sortedNames=" +
+ return "MySqlRowDescriptor{metadata=" + Arrays.toString(originMetadata) + ", sortedNames=" +
Arrays.toString(nameSet.getSortedNames()) + '}';
}
@@ -113,7 +113,7 @@ MySqlColumnDescriptor[] unwrap() {
return originMetadata;
}
- static MySqlRowMetadata create(DefinitionMetadataMessage[] columns) {
+ static MySqlRowDescriptor create(DefinitionMetadataMessage[] columns) {
int size = columns.length;
MySqlColumnDescriptor[] metadata = new MySqlColumnDescriptor[size];
@@ -121,7 +121,7 @@ static MySqlRowMetadata create(DefinitionMetadataMessage[] columns) {
metadata[i] = MySqlColumnDescriptor.create(i, columns[i]);
}
- return new MySqlRowMetadata(metadata);
+ return new MySqlRowDescriptor(metadata);
}
private static String[] getNames(MySqlColumnDescriptor[] metadata) {
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlResult.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSegmentResult.java
similarity index 89%
rename from r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlResult.java
rename to r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSegmentResult.java
index 4fd4b5196..d38f6cd91 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlResult.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSegmentResult.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
+import io.asyncer.r2dbc.mysql.api.MySqlRow;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import io.asyncer.r2dbc.mysql.internal.util.NettyBufferUtils;
import io.asyncer.r2dbc.mysql.internal.util.OperatorUtils;
@@ -49,16 +51,16 @@
import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull;
/**
- * An implementation of {@link Result} representing the results of a query against the MySQL database.
+ * An implementation of {@link MySqlResult} representing the results of a query against the MySQL database.
*
* A {@link Segment} provided by this implementation may be both {@link UpdateCount} and {@link RowSegment},
* see also {@link MySqlOkSegment}.
*/
-public final class MySqlResult implements Result {
+final class MySqlSegmentResult implements MySqlResult {
private final Flux segments;
- private MySqlResult(Flux segments) {
+ private MySqlSegmentResult(Flux segments) {
this.segments = segments;
}
@@ -81,7 +83,7 @@ public Flux map(BiFunction f) {
return segments.handle((segment, sink) -> {
if (segment instanceof RowSegment) {
- Row row = ((RowSegment) segment).row();
+ MySqlRow row = ((RowSegment) segment).row();
try {
sink.next(f.apply(row, row.getMetadata()));
@@ -116,10 +118,10 @@ public Flux map(Function super Readable, ? extends T> f) {
}
@Override
- public MySqlResult filter(Predicate filter) {
+ public MySqlResult filter(Predicate filter) {
requireNonNull(filter, "filter must not be null");
- return new MySqlResult(segments.filter(segment -> {
+ return new MySqlSegmentResult(segments.filter(segment -> {
if (filter.test(segment)) {
return true;
}
@@ -133,7 +135,7 @@ public MySqlResult filter(Predicate filter) {
}
@Override
- public Flux flatMap(Function> f) {
+ public Flux flatMap(Function> f) {
requireNonNull(f, "mapping function must not be null");
return segments.flatMap(segment -> {
@@ -160,7 +162,7 @@ static MySqlResult toResult(boolean binary, Codecs codecs, ConnectionContext con
requireNonNull(context, "context must not be null");
requireNonNull(messages, "messages must not be null");
- return new MySqlResult(OperatorUtils.discardOnCancel(messages)
+ return new MySqlSegmentResult(OperatorUtils.discardOnCancel(messages)
.doOnDiscard(ReferenceCounted.class, ReferenceCounted::release)
.handle(new MySqlSegments(binary, codecs, context, syntheticKeyName)));
}
@@ -200,14 +202,14 @@ private static final class MySqlRowSegment extends AbstractReferenceCounted impl
private final FieldValue[] fields;
- private MySqlRowSegment(FieldValue[] fields, MySqlRowMetadata metadata, Codecs codecs, boolean binary,
+ private MySqlRowSegment(FieldValue[] fields, MySqlRowDescriptor metadata, Codecs codecs, boolean binary,
ConnectionContext context) {
- this.row = new MySqlRow(fields, metadata, codecs, binary, context);
+ this.row = new MySqlDataRow(fields, metadata, codecs, binary, context);
this.fields = fields;
}
@Override
- public Row row() {
+ public MySqlRow row() {
return row;
}
@@ -258,7 +260,7 @@ private MySqlOkSegment(long rows, long lastInsertId, Codecs codecs, String keyNa
}
@Override
- public Row row() {
+ public MySqlRow row() {
return new InsertSyntheticRow(codecs, keyName, lastInsertId);
}
}
@@ -276,7 +278,7 @@ private static final class MySqlSegments implements BiConsumer sink) {
// Updated rows can be identified either by OK or rows in case of RETURNING
rowCount.getAndIncrement();
- MySqlRowMetadata metadata = this.rowMetadata;
+ MySqlRowDescriptor metadata = this.rowMetadata;
if (metadata == null) {
ReferenceCountUtil.safeRelease(message);
- sink.error(new IllegalStateException("No MySqlRowMetadata available"));
+ sink.error(new IllegalStateException("No metadata available"));
return;
}
@@ -316,7 +318,7 @@ public void accept(ServerMessage message, SynchronousSink sink) {
return;
}
- this.rowMetadata = MySqlRowMetadata.create(metadataMessages);
+ this.rowMetadata = MySqlRowDescriptor.create(metadataMessages);
} else if (message instanceof OkMessage) {
OkMessage msg = (OkMessage) message;
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnection.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnection.java
similarity index 96%
rename from r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnection.java
rename to r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnection.java
index 893c519a2..9f02bf500 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnection.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnection.java
@@ -16,6 +16,12 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlBatch;
+import io.asyncer.r2dbc.mysql.api.MySqlConnection;
+import io.asyncer.r2dbc.mysql.api.MySqlConnectionMetadata;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
+import io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition;
import io.asyncer.r2dbc.mysql.cache.PrepareCache;
import io.asyncer.r2dbc.mysql.cache.QueryCache;
import io.asyncer.r2dbc.mysql.client.Client;
@@ -30,9 +36,7 @@
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
-import io.r2dbc.spi.Connection;
import io.r2dbc.spi.IsolationLevel;
-import io.r2dbc.spi.Lifecycle;
import io.r2dbc.spi.R2dbcNonTransientResourceException;
import io.r2dbc.spi.Readable;
import io.r2dbc.spi.TransactionDefinition;
@@ -54,11 +58,11 @@
import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull;
/**
- * An implementation of {@link Connection} for connecting to the MySQL database.
+ * An implementation of {@link MySqlConnection} for connecting to the MySQL database.
*/
-public final class MySqlConnection implements Connection, Lifecycle, ConnectionState {
+final class MySqlSimpleConnection implements MySqlConnection, ConnectionState {
- private static final InternalLogger logger = InternalLoggerFactory.getInstance(MySqlConnection.class);
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(MySqlSimpleConnection.class);
private static final int DEFAULT_LOCK_WAIT_TIMEOUT = 50;
@@ -170,7 +174,7 @@ public final class MySqlConnection implements Connection, Lifecycle, ConnectionS
*/
private volatile long currentLockWaitTimeout;
- MySqlConnection(Client client, ConnectionContext context, Codecs codecs, IsolationLevel level,
+ MySqlSimpleConnection(Client client, ConnectionContext context, Codecs codecs, IsolationLevel level,
long lockWaitTimeout, QueryCache queryCache, PrepareCache prepareCache, @Nullable String product,
@Nullable Predicate prepare) {
this.client = client;
@@ -182,7 +186,8 @@ public final class MySqlConnection implements Connection, Lifecycle, ConnectionS
this.currentLockWaitTimeout = lockWaitTimeout;
this.queryCache = queryCache;
this.prepareCache = prepareCache;
- this.metadata = new MySqlConnectionMetadata(context.getServerVersion().toString(), product);
+ this.metadata = new MySqlSimpleConnectionMetadata(context.getServerVersion().toString(), product,
+ context.isMariaDb());
this.batchSupported = context.getCapability().isMultiStatementsAllowed();
this.prepare = prepare;
@@ -486,7 +491,7 @@ static Mono init(
context.setTimeZone(timeZone);
}
- return new MySqlConnection(client, context, codecs, data.level, data.lockWaitTimeout,
+ return new MySqlSimpleConnection(client, context, codecs, data.level, data.lockWaitTimeout,
queryCache, prepareCache, data.product, prepare);
});
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnectionMetadata.java
similarity index 76%
rename from r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionMetadata.java
rename to r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnectionMetadata.java
index af40495f5..ee7faf42d 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionMetadata.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnectionMetadata.java
@@ -16,7 +16,7 @@
package io.asyncer.r2dbc.mysql;
-import io.r2dbc.spi.ConnectionMetadata;
+import io.asyncer.r2dbc.mysql.api.MySqlConnectionMetadata;
import org.jetbrains.annotations.Nullable;
import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull;
@@ -24,15 +24,18 @@
/**
* Connection metadata for a connection connected to MySQL database.
*/
-public final class MySqlConnectionMetadata implements ConnectionMetadata {
+final class MySqlSimpleConnectionMetadata implements MySqlConnectionMetadata {
private final String version;
private final String product;
- MySqlConnectionMetadata(String version, @Nullable String product) {
+ private final boolean isMariaDb;
+
+ MySqlSimpleConnectionMetadata(String version, @Nullable String product, boolean isMariaDb) {
this.version = requireNonNull(version, "version must not be null");
this.product = product == null ? "Unknown" : product;
+ this.isMariaDb = isMariaDb;
}
@Override
@@ -40,6 +43,11 @@ public String getDatabaseVersion() {
return version;
}
+ @Override
+ public boolean isMariaDb() {
+ return isMariaDb;
+ }
+
@Override
public String getDatabaseProductName() {
return product;
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatement.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatement.java
deleted file mode 100644
index a228681f1..000000000
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatement.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2023 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 io.r2dbc.spi.Statement;
-import reactor.core.publisher.Flux;
-
-import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.require;
-import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull;
-
-/**
- * A strongly typed implementation of {@link Statement} for the MySQL database.
- */
-public interface MySqlStatement extends Statement {
-
- /**
- * {@inheritDoc}
- */
- @Override
- MySqlStatement add();
-
- /**
- * {@inheritDoc}
- */
- @Override
- MySqlStatement bind(int index, Object value);
-
- /**
- * {@inheritDoc}
- */
- @Override
- MySqlStatement bind(String name, Object value);
-
- /**
- * {@inheritDoc}
- */
- @Override
- MySqlStatement bindNull(int index, Class> type);
-
- /**
- * {@inheritDoc}
- */
- @Override
- MySqlStatement bindNull(String name, Class> type);
-
- /**
- * {@inheritDoc}
- */
- @Override
- Flux execute();
-
- /**
- * {@inheritDoc}
- */
- @Override
- default MySqlStatement returnGeneratedValues(String... columns) {
- requireNonNull(columns, "columns must not be null");
- return this;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- default MySqlStatement fetchSize(int rows) {
- require(rows >= 0, "Fetch size must be greater or equal to zero");
- return this;
- }
-}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatementSupport.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatementSupport.java
index 696626ba0..d976b6155 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatementSupport.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatementSupport.java
@@ -16,6 +16,7 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
import io.asyncer.r2dbc.mysql.internal.util.InternalArrays;
import org.jetbrains.annotations.Nullable;
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSyntheticBatch.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSyntheticBatch.java
index 87325591e..efc677beb 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSyntheticBatch.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlSyntheticBatch.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlBatch;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import reactor.core.publisher.Flux;
@@ -29,7 +31,7 @@
* An implementation of {@link MySqlBatch} for executing a collection of statements in one-by-one against the
* MySQL database.
*/
-final class MySqlSyntheticBatch extends MySqlBatch {
+final class MySqlSyntheticBatch implements MySqlBatch {
private final Client client;
@@ -54,7 +56,7 @@ public MySqlBatch add(String sql) {
@Override
public Flux execute() {
return QueryFlow.execute(client, statements)
- .map(messages -> MySqlResult.toResult(false, codecs, context, null, messages));
+ .map(messages -> MySqlSegmentResult.toResult(false, codecs, context, null, messages));
}
@Override
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinition.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinition.java
index 4abc58e36..de8091d01 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinition.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinition.java
@@ -33,7 +33,9 @@
* and 1073741824.
*
* @since 0.9.0
+ * @deprecated use {@link io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition} instead.
*/
+@Deprecated
public final class MySqlTransactionDefinition implements TransactionDefinition {
/**
@@ -43,7 +45,8 @@ public final class MySqlTransactionDefinition implements TransactionDefinition {
* same as if a {@code START TRANSACTION} followed by a {@code SELECT ...} from any InnoDB table was
* issued.
*/
- public static final Option WITH_CONSISTENT_SNAPSHOT = Option.valueOf("withConsistentSnapshot");
+ public static final Option WITH_CONSISTENT_SNAPSHOT =
+ io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition.WITH_CONSISTENT_SNAPSHOT;
/**
* Use {@code WITH CONSISTENT [engine] SNAPSHOT} for Facebook/MySQL or similar property. Only available
@@ -52,8 +55,8 @@ public final class MySqlTransactionDefinition implements TransactionDefinition {
* Note: This is an extended syntax based on specific distributions. Please check whether the server
* supports this property before using it.
*/
- public static final Option CONSISTENT_SNAPSHOT_ENGINE =
- Option.valueOf("consistentSnapshotEngine");
+ public static final Option> CONSISTENT_SNAPSHOT_ENGINE =
+ io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_ENGINE;
/**
* Use {@code WITH CONSISTENT SNAPSHOT FROM SESSION [session_id]} for Percona/MySQL or similar property.
@@ -67,7 +70,7 @@ public final class MySqlTransactionDefinition implements TransactionDefinition {
* supports this property before using it.
*/
public static final Option CONSISTENT_SNAPSHOT_FROM_SESSION =
- Option.valueOf("consistentSnapshotFromSession");
+ io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_FROM_SESSION;
private static final MySqlTransactionDefinition EMPTY =
new MySqlTransactionDefinition(Collections.emptyMap());
@@ -186,7 +189,7 @@ public Builder withConsistentSnapshot(@Nullable Boolean withConsistentSnapshot)
* @return this builder.
*/
public Builder consistentSnapshotEngine(@Nullable ConsistentSnapshotEngine snapshotEngine) {
- return option(CONSISTENT_SNAPSHOT_ENGINE, snapshotEngine);
+ return option(CONSISTENT_SNAPSHOT_ENGINE, snapshotEngine == null ? null : snapshotEngine.asSql());
}
/**
@@ -199,7 +202,7 @@ public Builder consistentSnapshotFromSession(@Nullable Long sessionId) {
return option(CONSISTENT_SNAPSHOT_FROM_SESSION, sessionId);
}
- private Builder option(Option key, @Nullable T value) {
+ private Builder option(Option> key, @Nullable Object value) {
if (value == null) {
this.options.remove(key);
} else {
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadata.java
index 4ef11772c..58fda659d 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadata.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadata.java
@@ -16,59 +16,92 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlNativeTypeMetadata;
+import io.asyncer.r2dbc.mysql.collation.CharCollation;
+
/**
- * A metadata descriptor considers MySQL types.
+ * An implementation of {@link MySqlNativeTypeMetadata}.
*/
-public final class MySqlTypeMetadata {
+final class MySqlTypeMetadata implements MySqlNativeTypeMetadata {
- private final int id;
+ private static final short NOT_NULL = 1;
- private final ColumnDefinition definition;
+// public static final short PRIMARY_PART = 1 << 1; // This field is a part of the primary key
+// public static final short UNIQUE_PART = 1 << 2; // This field is a part of a unique key
+// public static final short KEY_PART = 1 << 3; // This field is a part of a normal key
+// public static final short BLOB = 1 << 4;
- MySqlTypeMetadata(int id, ColumnDefinition definition) {
- this.id = id;
- this.definition = definition;
- }
+ private static final short UNSIGNED = 1 << 5;
+
+// public static final short ZEROFILL = 1 << 6;
+
+ public static final short BINARY = 1 << 7;
+
+ private static final short ENUM = 1 << 8;
+
+// public static final short AUTO_INCREMENT = 1 << 9;
+// public static final short TIMESTAMP = 1 << 10;
+
+ private static final short SET = 1 << 11; // type is set
+
+// public static final short NO_DEFAULT = 1 << 12; // column has no default value
+// public static final short ON_UPDATE_NOW = 1 << 13; // field will be set to NOW() in UPDATE statement
+
+ private static final short ALL_USED = NOT_NULL | UNSIGNED | BINARY | ENUM | SET;
+
+ private final int typeId;
/**
- * Get the native type identifier.
- *
- * @return the native type identifier.
+ * The original bitmap of definitions.
+ *
+ * MySQL uses 32-bits definition flags, but only returns the lower 16-bits.
*/
- public int getId() {
- return id;
- }
+ private final short definitions;
/**
- * Get the {@link ColumnDefinition} that potentially exposes more type differences.
- *
- * @return the column definitions.
+ * The character collation id of the column.
+ *
+ * collationId > 0 when protocol version == 4.1, 0 otherwise.
*/
- public ColumnDefinition getDefinition() {
- return definition;
+ private final int collationId;
+
+ MySqlTypeMetadata(int typeId, int definitions, int collationId) {
+ this.typeId = typeId;
+ this.definitions = (short) (definitions & ALL_USED);
+ this.collationId = collationId;
}
@Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof MySqlTypeMetadata)) {
- return false;
- }
+ public int getTypeId() {
+ return typeId;
+ }
- MySqlTypeMetadata that = (MySqlTypeMetadata) o;
+ @Override
+ public boolean isNotNull() {
+ return (definitions & NOT_NULL) != 0;
+ }
- return id == that.id && definition.equals(that.definition);
+ @Override
+ public boolean isUnsigned() {
+ return (definitions & UNSIGNED) != 0;
+ }
+
+ @Override
+ public boolean isBinary() {
+ // Utilize collationId to ascertain whether it is binary or not.
+ // This is necessary since the union of JSON columns, varchar binary, and char binary
+ // results in a bitmap with the BINARY flag set.
+ // see: https://github.com/asyncer-io/r2dbc-mysql/issues/91
+ return (collationId == 0 && (definitions & BINARY) != 0) || collationId == CharCollation.BINARY_ID;
}
@Override
- public int hashCode() {
- return 31 * id + definition.hashCode();
+ public boolean isEnum() {
+ return (definitions & ENUM) != 0;
}
@Override
- public String toString() {
- return "MySqlTypeMetadata(" + id + ", " + definition + ')';
+ public boolean isSet() {
+ return (definitions & SET) != 0;
}
}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ParametrizedStatementSupport.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ParametrizedStatementSupport.java
index fc67087a0..41ea8e465 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ParametrizedStatementSupport.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ParametrizedStatementSupport.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import reactor.core.publisher.Flux;
@@ -106,7 +108,7 @@ public final MySqlStatement bindNull(String name, Class> type) {
}
@Override
- public final Flux execute() {
+ public final Flux extends MySqlResult> execute() {
if (bindings.bindings.isEmpty()) {
throw new IllegalStateException("No parameters bound for current statement");
}
@@ -121,7 +123,7 @@ public final Flux execute() {
});
}
- protected abstract Flux execute(List bindings);
+ protected abstract Flux extends MySqlResult> execute(List bindings);
/**
* Get parameter index(es) by parameter name.
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PingStatement.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PingStatement.java
index d11717a34..7d834a9d7 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PingStatement.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PingStatement.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import reactor.core.publisher.Flux;
@@ -24,13 +26,13 @@
*/
final class PingStatement implements MySqlStatement {
- private final MySqlConnection connection;
+ private final MySqlSimpleConnection connection;
private final Codecs codecs;
private final ConnectionContext context;
- PingStatement(MySqlConnection connection, Codecs codecs, ConnectionContext context) {
+ PingStatement(MySqlSimpleConnection connection, Codecs codecs, ConnectionContext context) {
this.connection = connection;
this.codecs = codecs;
this.context = context;
@@ -63,7 +65,7 @@ public MySqlStatement bindNull(String name, Class> type) {
@Override
public Flux execute() {
- return Flux.just(MySqlResult.toResult(false, codecs, context, null,
+ return Flux.just(MySqlSegmentResult.toResult(false, codecs, context, null,
connection.doPingInternal()));
}
}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareParametrizedStatement.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareParametrizedStatement.java
index 3a946f3ea..9395a1309 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareParametrizedStatement.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareParametrizedStatement.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
import io.asyncer.r2dbc.mysql.cache.PrepareCache;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
@@ -47,7 +49,7 @@ public Flux execute(List bindings) {
StringUtils.extendReturning(query.getFormattedSql(), returningIdentifiers()),
bindings, fetchSize, prepareCache
))
- .map(messages -> MySqlResult.toResult(true, codecs, context, syntheticKeyName(), messages));
+ .map(messages -> MySqlSegmentResult.toResult(true, codecs, context, syntheticKeyName(), messages));
}
@Override
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareSimpleStatement.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareSimpleStatement.java
index 2284b991e..d037eda39 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareSimpleStatement.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/PrepareSimpleStatement.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
import io.asyncer.r2dbc.mysql.cache.PrepareCache;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
@@ -48,7 +50,7 @@ final class PrepareSimpleStatement extends SimpleStatementSupport {
public Flux execute() {
return Flux.defer(() -> QueryFlow.execute(client,
StringUtils.extendReturning(sql, returningIdentifiers()), BINDINGS, fetchSize, prepareCache))
- .map(messages -> MySqlResult.toResult(true, codecs, context, syntheticKeyName(), messages));
+ .map(messages -> MySqlSegmentResult.toResult(true, codecs, context, syntheticKeyName(), messages));
}
@Override
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/QueryFlow.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/QueryFlow.java
index afb23fb55..7b100cd24 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/QueryFlow.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/QueryFlow.java
@@ -16,6 +16,8 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlBatch;
+import io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition;
import io.asyncer.r2dbc.mysql.authentication.MySqlAuthProvider;
import io.asyncer.r2dbc.mysql.cache.PrepareCache;
import io.asyncer.r2dbc.mysql.client.Client;
@@ -1299,8 +1301,9 @@ static String buildStartTransaction(TransactionDefinition definition) {
boolean first = true;
if (Boolean.TRUE.equals(snapshot)) {
- ConsistentSnapshotEngine engine =
- definition.getAttribute(MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_ENGINE);
+ // Compatible for enum ConsistentSnapshotEngine.
+ Object eng = definition.getAttribute(MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_ENGINE);
+ String engine = eng == null ? null : eng.toString();
first = false;
builder.append(" WITH CONSISTENT ");
@@ -1308,7 +1311,7 @@ static String buildStartTransaction(TransactionDefinition definition) {
if (engine == null) {
builder.append("SNAPSHOT");
} else {
- builder.append(engine.asSql()).append(" SNAPSHOT");
+ builder.append(engine).append(" SNAPSHOT");
}
Long sessionId =
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/SimpleStatementSupport.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/SimpleStatementSupport.java
index 56b34a926..42ba279e3 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/SimpleStatementSupport.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/SimpleStatementSupport.java
@@ -16,6 +16,7 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlStatement;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextParametrizedStatement.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextParametrizedStatement.java
index e0fd475c6..88a10d1a1 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextParametrizedStatement.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextParametrizedStatement.java
@@ -16,6 +16,7 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import reactor.core.publisher.Flux;
@@ -35,6 +36,6 @@ final class TextParametrizedStatement extends ParametrizedStatementSupport {
protected Flux execute(List bindings) {
return Flux.defer(() -> QueryFlow.execute(client, query, returningIdentifiers(),
bindings))
- .map(messages -> MySqlResult.toResult(false, codecs, context, syntheticKeyName(), messages));
+ .map(messages -> MySqlSegmentResult.toResult(false, codecs, context, syntheticKeyName(), messages));
}
}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextSimpleStatement.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextSimpleStatement.java
index 04fd90001..a265f7af2 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextSimpleStatement.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/TextSimpleStatement.java
@@ -16,6 +16,7 @@
package io.asyncer.r2dbc.mysql;
+import io.asyncer.r2dbc.mysql.api.MySqlResult;
import io.asyncer.r2dbc.mysql.client.Client;
import io.asyncer.r2dbc.mysql.codec.Codecs;
import io.asyncer.r2dbc.mysql.internal.util.StringUtils;
@@ -35,6 +36,6 @@ public Flux execute() {
return Flux.defer(() -> QueryFlow.execute(
client,
StringUtils.extendReturning(sql, returningIdentifiers())
- ).map(messages -> MySqlResult.toResult(false, codecs, context, syntheticKeyName(), messages)));
+ ).map(messages -> MySqlSegmentResult.toResult(false, codecs, context, syntheticKeyName(), messages)));
}
}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlBatch.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlBatch.java
new file mode 100644
index 000000000..0a3a731f3
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlBatch.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.Batch;
+import reactor.core.publisher.Flux;
+
+/**
+ * {@link Batch} for executing a collection of statements in a batch against a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlBatch extends Batch {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalArgumentException if {@code sql} is {@code null}
+ */
+ @Override
+ MySqlBatch add(String sql);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Flux extends MySqlResult> execute();
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlColumnMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlColumnMetadata.java
new file mode 100644
index 000000000..9bd71a36a
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlColumnMetadata.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.ColumnMetadata;
+
+/**
+ * {@link ColumnMetadata} for column metadata returned from a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlColumnMetadata extends MySqlReadableMetadata, ColumnMetadata {
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnection.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnection.java
new file mode 100644
index 000000000..f9449a0d7
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnection.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.Connection;
+import io.r2dbc.spi.IsolationLevel;
+import io.r2dbc.spi.Lifecycle;
+import io.r2dbc.spi.TransactionDefinition;
+import io.r2dbc.spi.ValidationDepth;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+
+/**
+ * A {@link Connection} for connecting to a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlConnection extends Connection, Lifecycle {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono beginTransaction();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono beginTransaction(TransactionDefinition definition);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono close();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono commitTransaction();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ MySqlBatch createBatch();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono createSavepoint(String name);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ MySqlStatement createStatement(String sql);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ MySqlConnectionMetadata getMetadata();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono releaseSavepoint(String name);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono rollbackTransaction();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono rollbackTransactionToSavepoint(String name);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono setAutoCommit(boolean autoCommit);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono setLockWaitTimeout(Duration timeout);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono setStatementTimeout(Duration timeout);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono setTransactionIsolationLevel(IsolationLevel isolationLevel);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono validate(ValidationDepth depth);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono postAllocate();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ Mono preRelease();
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnectionMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnectionMetadata.java
new file mode 100644
index 000000000..9761b7918
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnectionMetadata.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.ConnectionMetadata;
+
+/**
+ * {@link ConnectionMetadata} for a connection connected to a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlConnectionMetadata extends ConnectionMetadata {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ String getDatabaseProductName();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ String getDatabaseVersion();
+
+ /**
+ * Checks if the connection is in MariaDB mode.
+ *
+ * @return {@code true} if it is MariaDB
+ */
+ boolean isMariaDb();
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlNativeTypeMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlNativeTypeMetadata.java
new file mode 100644
index 000000000..df3424869
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlNativeTypeMetadata.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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.api;
+
+/**
+ * An interface for MySQL native type metadata.
+ *
+ * @see MySqlReadableMetadata#getNativeTypeMetadata()
+ * @since 1.1.3
+ */
+public interface MySqlNativeTypeMetadata {
+
+ /**
+ * Gets the native type identifier, e.g. {@code 3} for {@code INT}.
+ *
+ * Note: It can not check if the current type is unsigned or not.
+ *
+ * @return the native type identifier
+ */
+ int getTypeId();
+
+ /**
+ * Checks if the value is not null.
+ *
+ * @return if value is not null
+ */
+ boolean isNotNull();
+
+ /**
+ * Checks if the value is an unsigned number. e.g. INT UNSIGNED, BIGINT UNSIGNED.
+ *
+ * Note: IEEE-754 floating types (e.g. DOUBLE/FLOAT) do not support it in MySQL 8.0+. When creating a
+ * column as an unsigned floating type, the server may report a warning.
+ *
+ * @return if value is an unsigned number
+ */
+ boolean isUnsigned();
+
+ /**
+ * Checks if the value is binary data.
+ *
+ * @return if value is binary data
+ */
+ boolean isBinary();
+
+ /**
+ * Checks if the value type is enum.
+ *
+ * @return if value is an enum
+ */
+ boolean isEnum();
+
+ /**
+ * Checks if the value type is set.
+ *
+ * @return if value is a set
+ */
+ boolean isSet();
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameterMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameterMetadata.java
new file mode 100644
index 000000000..a34d63f62
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameterMetadata.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.OutParameterMetadata;
+
+/**
+ * {@link OutParameterMetadata} for an {@code OUT} parameter metadata returned from a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlOutParameterMetadata extends MySqlReadableMetadata, OutParameterMetadata {
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatch.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameters.java
similarity index 61%
rename from r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatch.java
rename to r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameters.java
index 2fb0e431e..f6aab2037 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatch.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameters.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 asyncer.io projects
+ * Copyright 2024 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.
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package io.asyncer.r2dbc.mysql;
+package io.asyncer.r2dbc.mysql.api;
-import io.r2dbc.spi.Batch;
-import reactor.core.publisher.Flux;
+import io.r2dbc.spi.OutParameters;
/**
- * Base class considers methods definition for implementations of {@link Batch}.
+ * {@link OutParameters} for a collection of {@code OUT} parameters returned from a MySQL database.
+ *
+ * @since 1.1.3
*/
-public abstract class MySqlBatch implements Batch {
-
- @Override
- public abstract MySqlBatch add(String sql);
+public interface MySqlOutParameters extends MySqlReadable, OutParameters {
@Override
- public abstract Flux execute();
+ MySqlOutParametersMetadata getMetadata();
}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParametersMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParametersMetadata.java
new file mode 100644
index 000000000..318f18bf1
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParametersMetadata.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.OutParametersMetadata;
+
+import java.util.List;
+
+/**
+ * {@link OutParametersMetadata} for {@code OUT} parameters metadata returned from a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlOutParametersMetadata extends OutParametersMetadata {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ MySqlOutParameterMetadata getParameterMetadata(int index);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ MySqlOutParameterMetadata getParameterMetadata(String name);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ List extends MySqlOutParameterMetadata> getParameterMetadatas();
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlReadable.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlReadable.java
new file mode 100644
index 000000000..e47c806f0
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlReadable.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.Readable;
+
+/**
+ * {@link Readable Readable data} for a row or a collection of {@code OUT} parameters that's against a MySQL
+ * database.
+ *
+ * @see MySqlOutParameters
+ * @see MySqlRow
+ * @since 1.1.3
+ */
+public interface MySqlReadable extends Readable {
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlReadableMetadata.java
similarity index 68%
rename from r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnMetadata.java
rename to r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlReadableMetadata.java
index 162bf81e3..5ceceb438 100644
--- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlColumnMetadata.java
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlReadableMetadata.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 asyncer.io projects
+ * Copyright 2024 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.
@@ -14,17 +14,20 @@
* limitations under the License.
*/
-package io.asyncer.r2dbc.mysql;
+package io.asyncer.r2dbc.mysql.api;
import io.asyncer.r2dbc.mysql.codec.CodecContext;
import io.asyncer.r2dbc.mysql.collation.CharCollation;
import io.asyncer.r2dbc.mysql.constant.MySqlType;
-import io.r2dbc.spi.ColumnMetadata;
+import io.r2dbc.spi.ReadableMetadata;
/**
- * An abstraction of {@link ColumnMetadata} considers MySQL
+ * {@link ReadableMetadata} for metadata of a column or an {@code OUT} parameter returned from a MySQL
+ * database.
+ *
+ * @since 1.1.3
*/
-public interface MySqlColumnMetadata extends ColumnMetadata {
+public interface MySqlReadableMetadata extends ReadableMetadata {
/**
* {@inheritDoc}
@@ -33,26 +36,25 @@ public interface MySqlColumnMetadata extends ColumnMetadata {
MySqlType getType();
/**
- * {@inheritDoc}
- */
- @Override
- MySqlTypeMetadata getNativeTypeMetadata();
-
- /**
- * Gets the {@link CharCollation} used for stringification type. It will not be a binary collation.
+ * Gets the {@link CharCollation} used for stringification type. If server-side collation is binary, it
+ * will return the default client collation of {@code context}.
*
- * @param context the codec context for load the default character collation on the server-side.
+ * @param context the codec context for load the default character collation.
* @return the {@link CharCollation}.
*/
CharCollation getCharCollation(CodecContext context);
/**
- * Gets the field max size that's defined by the table, the original type is an unsigned int32.
- *
- * @return the field max size.
+ * {@inheritDoc}
*/
- long getNativePrecision();
+ @Override
+ default MySqlNativeTypeMetadata getNativeTypeMetadata() {
+ return null;
+ }
+ /**
+ * {@inheritDoc}
+ */
@Override
default Class> getJavaType() {
return getType().getJavaType();
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlResult.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlResult.java
new file mode 100644
index 000000000..940ef150d
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlResult.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.OutParameters;
+import io.r2dbc.spi.Readable;
+import io.r2dbc.spi.Result;
+import io.r2dbc.spi.Row;
+import io.r2dbc.spi.RowMetadata;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * A {@link Result} for results of a query against a MySQL database.
+ *
+ * Note: A query may return multiple {@link MySqlResult}s.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlResult extends Result {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return a {@link Mono} emitting the number of rows updated, or empty if it is not an update result.
+ */
+ @Override
+ Mono getRowsUpdated();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return a {@link Flux} of mapped results
+ * @throws IllegalArgumentException if {@code mappingFunction} is {@code null}
+ */
+ @Override
+ Flux map(BiFunction mappingFunction);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return a {@link Flux} of mapped results
+ * @throws IllegalArgumentException if {@code mappingFunction} is {@code null}
+ */
+ @Override
+ Flux map(Function super Readable, ? extends T> mappingFunction);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return a {@link MySqlResult} that will only emit results that match the {@code predicate}
+ * @throws IllegalArgumentException if {@code predicate} is {@code null}
+ */
+ @Override
+ MySqlResult filter(Predicate predicate);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return a {@link Flux} of mapped results
+ * @throws IllegalArgumentException if {@code mappingFunction} is {@code null}
+ */
+ @Override
+ Flux flatMap(Function> mappingFunction);
+
+ /**
+ * Marker interface for a MySQL result segment. Result segments represent the individual parts of a result
+ * from a query against a MySQL database. It is a sealed interface.
+ *
+ * @see RowSegment
+ * @see OutSegment
+ * @see UpdateCount
+ * @see Message
+ * @see OkSegment
+ */
+ interface Segment extends Result.Segment {
+ }
+
+ /**
+ * Row segment consisting of {@link Row row data}.
+ */
+ interface RowSegment extends Segment, Result.RowSegment {
+
+ /**
+ * Gets the {@link MySqlRow row data}.
+ *
+ * @return a {@link MySqlRow} of data
+ */
+ @Override
+ MySqlRow row();
+ }
+
+ /**
+ * Out parameters segment consisting of {@link OutParameters readable data}.
+ *
+ * In MySQL, {@code OUT} parameters are returned as a row. These rows will be preceded by a flag
+ * indicating that the following rows are {@code OUT} parameters. So, an {@link OutSegment} must be an
+ * {@link RowSegment}, but not vice versa.
+ */
+ interface OutSegment extends RowSegment, Result.OutSegment {
+
+ /**
+ * Gets the {@link OutParameters OUT parameters}.
+ *
+ * @return a {@link OutParameters} of data
+ */
+ @Override
+ MySqlOutParameters outParameters();
+ }
+
+ /**
+ * Update count segment consisting providing an {@link #value() affected rows count}.
+ */
+ interface UpdateCount extends Segment, Result.UpdateCount {
+ }
+
+ /**
+ * Message segment reported as result of the statement processing.
+ */
+ interface Message extends Segment, Result.Message {
+ }
+
+ /**
+ * Insert result segment consisting of a {@link #row() last inserted id} and
+ * {@link #value() affected rows count}, and only appears if the statement is an insert, the table has an
+ * auto-increment identifier column, and the statement is not using the {@code RETURNING} clause.
+ *
+ * Note: a {@link MySqlResult} will return only the last inserted id whatever how many rows are inserted.
+ */
+ interface OkSegment extends RowSegment, UpdateCount {
+ }
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRow.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRow.java
new file mode 100644
index 000000000..0b9e48f94
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRow.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.Row;
+
+/**
+ * A {@link Row} for a data row of a {@link MySqlResult}.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlRow extends MySqlReadable, Row {
+
+ /**
+ * Returns the {@link MySqlRowMetadata} for all columns in this row.
+ *
+ * @return the {@link MySqlRowMetadata} for all columns in this row
+ */
+ @Override
+ MySqlRowMetadata getMetadata();
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRowMetadata.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRowMetadata.java
new file mode 100644
index 000000000..492ae2d24
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRowMetadata.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.RowMetadata;
+
+import java.util.List;
+
+/**
+ * {@link RowMetadata} for a row metadata returned from a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlRowMetadata extends RowMetadata {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ MySqlColumnMetadata getColumnMetadata(int index);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ MySqlColumnMetadata getColumnMetadata(String name);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ List extends MySqlColumnMetadata> getColumnMetadatas();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ boolean contains(String columnName);
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlStatement.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlStatement.java
new file mode 100644
index 000000000..cd0744833
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlStatement.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.Statement;
+import reactor.core.publisher.Flux;
+
+import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.require;
+import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull;
+
+/**
+ * A strongly typed abstraction of {@link Statement} for a SQL statement against a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlStatement extends Statement {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link MySqlStatement this}
+ * @throws IllegalStateException if the current bindings are incomplete, or statement has been executed
+ */
+ @Override
+ MySqlStatement add();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link MySqlStatement this}
+ * @throws IllegalArgumentException if {@code value} is {@code null}, or {@code index} out of range
+ * @throws UnsupportedOperationException if the statement is not a parameterized statement
+ */
+ @Override
+ MySqlStatement bind(int index, Object value);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link MySqlStatement this}
+ * @throws IllegalArgumentException if {@code name} or {@code value} is {@code null}, or name not
+ * found
+ * @throws UnsupportedOperationException if the statement is not a parameterized statement
+ */
+ @Override
+ MySqlStatement bind(String name, Object value);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link MySqlStatement this}
+ * @throws IllegalArgumentException if {@code type} is {@code null}, or {@code index} out of range
+ * @throws UnsupportedOperationException if the statement is not a parameterized statement
+ */
+ @Override
+ MySqlStatement bindNull(int index, Class> type);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link MySqlStatement this}
+ * @throws IllegalArgumentException if {@code name} or {@code type} is {@code null}, or name not
+ * found
+ * @throws UnsupportedOperationException if the statement is not a parameterized statement
+ */
+ @Override
+ MySqlStatement bindNull(String name, Class> type);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return a {@link Flux} representing {@link MySqlResult}s of the statement
+ */
+ @Override
+ Flux extends MySqlResult> execute();
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link MySqlStatement this}
+ * @throws IllegalArgumentException if columns is {@code null}
+ */
+ @Override
+ default MySqlStatement returnGeneratedValues(String... columns) {
+ requireNonNull(columns, "columns must not be null");
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link MySqlStatement this}
+ * @throws IllegalArgumentException if fetch size is less than zero
+ */
+ @Override
+ default MySqlStatement fetchSize(int rows) {
+ require(rows >= 0, "Fetch size must be greater or equal to zero");
+ return this;
+ }
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinition.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinition.java
new file mode 100644
index 000000000..636a50678
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinition.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.IsolationLevel;
+import io.r2dbc.spi.Option;
+import io.r2dbc.spi.TransactionDefinition;
+
+import java.time.Duration;
+
+/**
+ * {@link TransactionDefinition} for a MySQL database.
+ *
+ * @since 1.1.3
+ */
+public interface MySqlTransactionDefinition extends TransactionDefinition {
+
+ /**
+ * Use {@code WITH CONSISTENT SNAPSHOT} property.
+ *
+ * The option starts a consistent read for storage engines such as InnoDB and XtraDB that can do so, the
+ * same as if a {@code START TRANSACTION} followed by a {@code SELECT ...} from any InnoDB table was
+ * issued.
+ */
+ Option WITH_CONSISTENT_SNAPSHOT = Option.valueOf("withConsistentSnapshot");
+
+ /**
+ * Use {@code WITH CONSISTENT [engine] SNAPSHOT} for Facebook/MySQL or similar property. Only available
+ * when {@link #WITH_CONSISTENT_SNAPSHOT} is set to {@code true}.
+ *
+ * Note: This is an extended syntax based on specific distributions. Please check whether the server
+ * supports this property before using it.
+ */
+ Option CONSISTENT_SNAPSHOT_ENGINE = Option.valueOf("consistentSnapshotEngine");
+
+ /**
+ * Use {@code WITH CONSISTENT SNAPSHOT FROM SESSION [session_id]} for Percona/MySQL or similar property.
+ * Only available when {@link #WITH_CONSISTENT_SNAPSHOT} is set to {@code true}.
+ *
+ * The {@code session_id} is received by {@code SHOW COLUMNS FROM performance_schema.processlist}, it
+ * should be an unsigned 64-bit integer. Use {@code SHOW PROCESSLIST} to find session identifier of the
+ * process list.
+ *
+ * Note: This is an extended syntax based on specific distributions. Please check whether the server
+ * supports this property before using it.
+ */
+ Option CONSISTENT_SNAPSHOT_FROM_SESSION = Option.valueOf("consistentSnapshotFromSession");
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying
+ * {@link IsolationLevel}.
+ *
+ * @param isolationLevel the isolation level to use during the transaction.
+ * @return a new {@link MySqlTransactionDefinition} with the {@code isolationLevel}.
+ * @throws IllegalArgumentException if {@code isolationLevel} is {@code null}.
+ */
+ MySqlTransactionDefinition isolationLevel(IsolationLevel isolationLevel);
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and using the default
+ * isolation level. Removes transaction isolation level if configured already.
+ *
+ * @return a new {@link MySqlTransactionDefinition} without specified isolation level.
+ */
+ MySqlTransactionDefinition withoutIsolationLevel();
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and using read-only
+ * transaction semantics. Overrides transaction mutability if configured already.
+ *
+ * @return a new {@link MySqlTransactionDefinition} with read-only semantics.
+ */
+ MySqlTransactionDefinition readOnly();
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and using explicitly
+ * read-write transaction semantics. Overrides transaction mutability if configured already.
+ *
+ * @return a new {@link MySqlTransactionDefinition} with read-write semantics.
+ */
+ MySqlTransactionDefinition readWrite();
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and avoid to using
+ * explicitly mutability. Removes transaction mutability if configured already.
+ *
+ * @return a new {@link MySqlTransactionDefinition} without explicitly mutability.
+ */
+ MySqlTransactionDefinition withoutMutability();
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying a lock wait
+ * timeout. Overrides transaction lock wait timeout if configured already.
+ *
+ * Note: for now, it is only available in InnoDB or InnoDB-compatible engines.
+ *
+ * @param timeout the lock wait timeout.
+ * @return a new {@link MySqlTransactionDefinition} with the {@code timeout}.
+ * @throws IllegalArgumentException if {@code timeout} is {@code null}.
+ */
+ MySqlTransactionDefinition lockWaitTimeout(Duration timeout);
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying to use the
+ * default lock wait timeout. Removes transaction lock wait timeout if configured already.
+ *
+ * @return a new {@link MySqlTransactionDefinition} without specified lock wait timeout.
+ */
+ MySqlTransactionDefinition withoutLockWaitTimeout();
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying to with
+ * consistent snapshot. Overrides transaction consistency if configured already.
+ *
+ * @return a new {@link MySqlTransactionDefinition} with consistent snapshot semantics.
+ */
+ MySqlTransactionDefinition consistent();
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying to with
+ * consistent engine snapshot. Overrides transaction consistency if configured already.
+ *
+ * @param engine the consistent snapshot engine, e.g. {@code ROCKSDB}.
+ * @return a new {@link MySqlTransactionDefinition} with consistent snapshot semantics.
+ * @throws IllegalArgumentException if {@code engine} is {@code null}.
+ */
+ MySqlTransactionDefinition consistent(String engine);
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying to with
+ * consistent engine snapshot from session. Overrides transaction consistency if configured already.
+ *
+ * @param engine the consistent snapshot engine, e.g. {@code ROCKSDB}.
+ * @param sessionId the session id.
+ * @return a new {@link MySqlTransactionDefinition} with consistent snapshot semantics.
+ * @throws IllegalArgumentException if {@code engine} is {@code null}.
+ */
+ MySqlTransactionDefinition consistent(String engine, long sessionId);
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying to with
+ * consistent snapshot from session. Overrides transaction consistency if configured already.
+ *
+ * @param sessionId the session id.
+ * @return a new {@link MySqlTransactionDefinition} with consistent snapshot semantics.
+ */
+ MySqlTransactionDefinition consistentFromSession(long sessionId);
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} retaining all configured options and applying to without
+ * consistent snapshot. Removes transaction consistency if configured already.
+ *
+ * @return a new {@link MySqlTransactionDefinition} without consistent snapshot semantics.
+ */
+ MySqlTransactionDefinition withoutConsistent();
+
+ /**
+ * Gets an empty {@link MySqlTransactionDefinition}.
+ *
+ * @return an empty {@link MySqlTransactionDefinition}.
+ */
+ static MySqlTransactionDefinition empty() {
+ return SimpleTransactionDefinition.EMPTY;
+ }
+
+ /**
+ * Creates a {@link MySqlTransactionDefinition} specifying transaction mutability.
+ *
+ * @param readWrite {@code true} for read-write, {@code false} to use a read-only transaction.
+ * @return a new {@link MySqlTransactionDefinition} using the specified transaction mutability.
+ */
+ static MySqlTransactionDefinition mutability(boolean readWrite) {
+ return readWrite ? SimpleTransactionDefinition.EMPTY.readWrite() :
+ SimpleTransactionDefinition.EMPTY.readOnly();
+ }
+
+ static MySqlTransactionDefinition from(IsolationLevel isolationLevel) {
+ return SimpleTransactionDefinition.EMPTY.isolationLevel(isolationLevel);
+ }
+}
diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/SimpleTransactionDefinition.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/SimpleTransactionDefinition.java
new file mode 100644
index 000000000..a6db9dddc
--- /dev/null
+++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/SimpleTransactionDefinition.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2024 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.api;
+
+import io.r2dbc.spi.IsolationLevel;
+import io.r2dbc.spi.Option;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.requireNonNull;
+
+/**
+ * An implementation of {@link MySqlTransactionDefinition} for immutable transaction definition.
+ *
+ * @since 1.1.3
+ */
+final class SimpleTransactionDefinition implements MySqlTransactionDefinition {
+
+ static final SimpleTransactionDefinition EMPTY = new SimpleTransactionDefinition(Collections.emptyMap());
+
+ private final Map