From f5fee0442434ac85da11bf1aa3c968e9b37e46e1 Mon Sep 17 00:00:00 2001 From: Mirro Mutth Date: Thu, 29 Feb 2024 12:16:26 +0900 Subject: [PATCH] Extract public API to api package - Use strongly typed interfaces - Standardize R2DBC driver MySQL-specific API --- .../asyncer/r2dbc/mysql/ColumnDefinition.java | 168 ---------- .../asyncer/r2dbc/mysql/ConnectionState.java | 2 +- .../r2dbc/mysql/ConsistentSnapshotEngine.java | 7 +- .../r2dbc/mysql/InsertSyntheticRow.java | 21 +- .../r2dbc/mysql/MySqlBatchingBatch.java | 6 +- .../r2dbc/mysql/MySqlColumnDescriptor.java | 29 +- .../r2dbc/mysql/MySqlConnectionFactory.java | 9 +- .../{MySqlRow.java => MySqlDataRow.java} | 34 +- ...wMetadata.java => MySqlRowDescriptor.java} | 14 +- ...SqlResult.java => MySqlSegmentResult.java} | 34 +- ...ection.java => MySqlSimpleConnection.java} | 27 +- ...ava => MySqlSimpleConnectionMetadata.java} | 14 +- .../asyncer/r2dbc/mysql/MySqlStatement.java | 83 ----- .../r2dbc/mysql/MySqlStatementSupport.java | 1 + .../r2dbc/mysql/MySqlSyntheticBatch.java | 6 +- .../mysql/MySqlTransactionDefinition.java | 15 +- .../r2dbc/mysql/MySqlTypeMetadata.java | 106 ++++-- .../mysql/ParametrizedStatementSupport.java | 6 +- .../io/asyncer/r2dbc/mysql/PingStatement.java | 16 +- .../mysql/PrepareParametrizedStatement.java | 4 +- .../r2dbc/mysql/PrepareSimpleStatement.java | 4 +- .../io/asyncer/r2dbc/mysql/QueryFlow.java | 9 +- .../r2dbc/mysql/SimpleStatementSupport.java | 1 + .../mysql/TextParametrizedStatement.java | 3 +- .../r2dbc/mysql/TextSimpleStatement.java | 3 +- .../asyncer/r2dbc/mysql/api/MySqlBatch.java | 46 +++ .../MySqlColumnMetadata.java} | 19 +- .../r2dbc/mysql/api/MySqlConnection.java | 203 +++++++++++ .../mysql/api/MySqlConnectionMetadata.java | 52 +++ .../mysql/api/MySqlNativeTypeMetadata.java | 74 ++++ .../mysql/api/MySqlOutParameterMetadata.java | 27 ++ .../r2dbc/mysql/api/MySqlOutParameters.java | 35 ++ .../mysql/api/MySqlOutParametersMetadata.java | 59 ++++ .../r2dbc/mysql/api/MySqlReadable.java | 30 ++ .../MySqlReadableMetadata.java} | 42 ++- .../asyncer/r2dbc/mysql/api/MySqlResult.java | 173 ++++++++++ .../io/asyncer/r2dbc/mysql/api/MySqlRow.java | 75 +++++ .../r2dbc/mysql/api/MySqlRowMetadata.java | 70 ++++ .../r2dbc/mysql/api/MySqlStatement.java | 129 +++++++ .../mysql/api/MySqlTransactionDefinition.java | 194 +++++++++++ .../api/SimpleTransactionDefinition.java | 218 ++++++++++++ .../asyncer/r2dbc/mysql/api/package-info.java | 24 ++ .../mysql/codec/AbstractClassedCodec.java | 6 +- .../mysql/codec/AbstractPrimitiveCodec.java | 4 +- .../r2dbc/mysql/codec/BigDecimalCodec.java | 6 +- .../r2dbc/mysql/codec/BigIntegerCodec.java | 6 +- .../r2dbc/mysql/codec/BitSetCodec.java | 6 +- .../asyncer/r2dbc/mysql/codec/BlobCodec.java | 8 +- .../r2dbc/mysql/codec/BooleanCodec.java | 9 +- .../r2dbc/mysql/codec/ByteArrayCodec.java | 6 +- .../r2dbc/mysql/codec/ByteBufferCodec.java | 6 +- .../asyncer/r2dbc/mysql/codec/ByteCodec.java | 6 +- .../asyncer/r2dbc/mysql/codec/ClobCodec.java | 8 +- .../io/asyncer/r2dbc/mysql/codec/Codec.java | 10 +- .../io/asyncer/r2dbc/mysql/codec/Codecs.java | 10 +- .../r2dbc/mysql/codec/DefaultCodecs.java | 18 +- .../r2dbc/mysql/codec/DoubleCodec.java | 6 +- .../r2dbc/mysql/codec/DurationCodec.java | 6 +- .../asyncer/r2dbc/mysql/codec/EnumCodec.java | 6 +- .../asyncer/r2dbc/mysql/codec/FloatCodec.java | 6 +- .../r2dbc/mysql/codec/InstantCodec.java | 6 +- .../r2dbc/mysql/codec/IntegerCodec.java | 6 +- .../r2dbc/mysql/codec/LocalDateCodec.java | 6 +- .../r2dbc/mysql/codec/LocalDateTimeCodec.java | 10 +- .../r2dbc/mysql/codec/LocalTimeCodec.java | 6 +- .../asyncer/r2dbc/mysql/codec/LongCodec.java | 6 +- .../r2dbc/mysql/codec/MassiveCodec.java | 6 +- .../mysql/codec/MassiveParametrizedCodec.java | 9 +- .../mysql/codec/OffsetDateTimeCodec.java | 6 +- .../r2dbc/mysql/codec/OffsetTimeCodec.java | 6 +- .../r2dbc/mysql/codec/ParametrizedCodec.java | 10 +- .../r2dbc/mysql/codec/PrimitiveCodec.java | 10 +- .../asyncer/r2dbc/mysql/codec/SetCodec.java | 10 +- .../asyncer/r2dbc/mysql/codec/ShortCodec.java | 6 +- .../r2dbc/mysql/codec/StringCodec.java | 6 +- .../asyncer/r2dbc/mysql/codec/YearCodec.java | 6 +- .../r2dbc/mysql/codec/ZonedDateTimeCodec.java | 10 +- .../r2dbc/mysql/constant/MySqlType.java | 30 +- .../server/DefinitionMetadataMessage.java | 28 +- .../mysql/message/server/RowMessage.java | 6 +- .../r2dbc/mysql/ColumnDefinitionTest.java | 61 ---- .../mysql/ConnectionIntegrationTest.java | 315 +++++++++--------- .../r2dbc/mysql/InitDbIntegrationTest.java | 1 + .../r2dbc/mysql/IntegrationTestSupport.java | 3 +- .../mysql/JacksonIntegrationTestSupport.java | 7 +- .../mysql/MariaDbIntegrationTestSupport.java | 1 + ...st.java => MySqlSimpleConnectionTest.java} | 12 +- .../mysql/MySqlTransactionDefinitionTest.java | 80 ----- .../r2dbc/mysql/MySqlTypeMetadataTest.java | 60 ++++ .../mysql/QueryIntegrationTestSupport.java | 12 +- .../r2dbc/mysql/SslTunnelIntegrationTest.java | 1 + .../mysql/StartTransactionStateTest.java | 49 ++- .../r2dbc/mysql/StatementTestSupport.java | 1 + .../r2dbc/mysql/TimeZoneIntegrationTest.java | 1 + .../api/MySqlTransactionDefinitionTest.java | 172 ++++++++++ .../asyncer/r2dbc/mysql/codec/CodecsTest.java | 13 +- .../asyncer/r2dbc/mysql/codec/Decoding.java | 12 +- .../r2dbc/mysql/json/JacksonCodec.java | 13 +- 98 files changed, 2288 insertions(+), 940 deletions(-) delete mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/ColumnDefinition.java rename r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/{MySqlRow.java => MySqlDataRow.java} (68%) rename r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/{MySqlRowMetadata.java => MySqlRowDescriptor.java} (88%) rename r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/{MySqlResult.java => MySqlSegmentResult.java} (89%) rename r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/{MySqlConnection.java => MySqlSimpleConnection.java} (95%) rename r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/{MySqlConnectionMetadata.java => MySqlSimpleConnectionMetadata.java} (76%) delete mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlStatement.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlBatch.java rename r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/{MySqlBatch.java => api/MySqlColumnMetadata.java} (59%) create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnection.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnectionMetadata.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlNativeTypeMetadata.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameterMetadata.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameters.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParametersMetadata.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlReadable.java rename r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/{MySqlColumnMetadata.java => api/MySqlReadableMetadata.java} (61%) create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlResult.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRow.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRowMetadata.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlStatement.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinition.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/SimpleTransactionDefinition.java create mode 100644 r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/package-info.java delete mode 100644 r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ColumnDefinitionTest.java rename r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/{MySqlConnectionTest.java => MySqlSimpleConnectionTest.java} (91%) delete mode 100644 r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinitionTest.java create mode 100644 r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadataTest.java create mode 100644 r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinitionTest.java 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 client; - private MySqlConnectionFactory(Mono client) { + private MySqlConnectionFactory(Mono client) { this.client = client; } @Override - public Mono create() { + public Mono 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 68% 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..a9e56b747 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,11 +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 +29,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 +44,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"); @@ -69,16 +69,7 @@ public T get(String name, Class type) { return codecs.decode(fields[info.getIndex()], info, type, binary, context); } - /** - * Returns the value for a column in this row. The value can be a parameterized type. - * - * @param index the index of the column starting at {@code 0}. - * @param type the parameterized type of item to return. - * @param the type of the item being returned. - * @return the value for a column in this row. Value can be {@code null}. - * @throws IllegalArgumentException if {@code name} or {@code type} is {@code null}. - */ - @Nullable + @Override public T get(int index, ParameterizedType type) { requireNonNull(type, "type must not be null"); @@ -86,16 +77,7 @@ public T get(int index, ParameterizedType type) { return codecs.decode(fields[index], info, type, binary, context); } - /** - * Returns the value for a column in this row. The value can be a parameterized type. - * - * @param name the name of the column. - * @param type the parameterized type of item to return. - * @param the type of the item being returned. - * @return the value for a column in this row. Value can be {@code null}. - * @throws IllegalArgumentException if {@code name} or {@code type} is {@code null}. - */ - @Nullable + @Override public T get(String name, ParameterizedType type) { requireNonNull(type, "type must not be null"); @@ -107,7 +89,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 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 95% 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..f4a2b3746 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; @@ -239,7 +244,7 @@ public MySqlStatement createStatement(String sql) { requireNonNull(sql, "sql must not be null"); if (sql.startsWith(PING_MARKER)) { - return new PingStatement(this, codecs, context); + return new PingStatement(codecs, context, Flux.defer(this::doPingInternal)); } Query query = queryCache.get(sql); @@ -451,11 +456,11 @@ public Mono setStatementTimeout(Duration timeout) { ); } - Flux doPingInternal() { + private Flux doPingInternal() { return client.exchange(PingMessage.INSTANCE, PING); } - boolean isSessionAutoCommit() { + private boolean isSessionAutoCommit() { return (context.getServerStatuses() & ServerStatuses.AUTO_COMMIT) != 0; } @@ -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..cde67f805 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,36 +16,94 @@ 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 int getTypeId() { + return typeId; + } + + @Override + public boolean isNotNull() { + return (definitions & NOT_NULL) != 0; + } + + @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 + // FIXME: use collationId to check, definitions is not reliable even in protocol version < 4.1 + return (collationId == 0 && (definitions & BINARY) != 0) || collationId == CharCollation.BINARY_ID; + } + + @Override + public boolean isEnum() { + return (definitions & ENUM) != 0; + } + + @Override + public boolean isSet() { + return (definitions & SET) != 0; } @Override @@ -59,16 +117,20 @@ public boolean equals(Object o) { MySqlTypeMetadata that = (MySqlTypeMetadata) o; - return id == that.id && definition.equals(that.definition); + return typeId == that.typeId && definitions == that.definitions && collationId == that.collationId; } @Override public int hashCode() { - return 31 * id + definition.hashCode(); + int result = 31 * typeId + (int) definitions; + return 31 * result + collationId; } @Override public String toString() { - return "MySqlTypeMetadata(" + id + ", " + definition + ')'; + return "MySqlTypeMetadata{typeId=" + typeId + + ", definitions=0x" + Integer.toHexString(definitions & ALL_USED) + + ", collationId=" + collationId + + '}'; } } 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 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 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..f8b1936e2 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,7 +16,10 @@ 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 io.asyncer.r2dbc.mysql.message.server.ServerMessage; import reactor.core.publisher.Flux; /** @@ -24,16 +27,16 @@ */ final class PingStatement implements MySqlStatement { - private final MySqlConnection connection; - private final Codecs codecs; private final ConnectionContext context; - PingStatement(MySqlConnection connection, Codecs codecs, ConnectionContext context) { - this.connection = connection; + private final Flux deferred; + + PingStatement(Codecs codecs, ConnectionContext context, Flux deferred) { this.codecs = codecs; this.context = context; + this.deferred = deferred; } @Override @@ -63,7 +66,8 @@ public MySqlStatement bindNull(String name, Class type) { @Override public Flux execute() { - return Flux.just(MySqlResult.toResult(false, codecs, context, null, - connection.doPingInternal())); + return Flux.just( + MySqlSegmentResult.toResult(false, codecs, context, null, deferred) + ); } } 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..d49ca9b1e --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlBatch.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.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} + * + * @param sql the statement to add + * @return {@link MySqlBatch this} + * @throws IllegalArgumentException if {@code sql} is {@code null} + */ + @Override + MySqlBatch add(String sql); + + /** + * {@inheritDoc} + * + * @return the {@link MySqlResult}s of executing the batch + */ + @Override + Flux execute(); +} 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/MySqlColumnMetadata.java similarity index 59% 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/MySqlColumnMetadata.java index 2fb0e431e..9bd71a36a 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlBatch.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlColumnMetadata.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,14 @@ * 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.ColumnMetadata; /** - * Base class considers methods definition for implementations of {@link Batch}. + * {@link ColumnMetadata} for column metadata returned from a MySQL database. + * + * @since 1.1.3 */ -public abstract class MySqlBatch implements Batch { - - @Override - public abstract MySqlBatch add(String sql); - - @Override - public abstract Flux execute(); +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..e2b5b2244 --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnection.java @@ -0,0 +1,203 @@ +/* + * 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} + *

+ * Note: MySQL server will disable the auto-commit mode automatically when a transaction is started. + * + * @return a {@link Mono} that indicates that the transaction has begun + */ + @Override + Mono beginTransaction(); + + /** + * {@inheritDoc} + *

+ * Note: MySQL server will disable the auto-commit mode automatically when a transaction is started. + * + * @param definition the transaction definition, must not be {@code null} + * @return a {@link Mono} that indicates that the transaction has begun + * @throws IllegalArgumentException if {@code definition} is {@code null} + */ + @Override + Mono beginTransaction(TransactionDefinition definition); + + /** + * {@inheritDoc} + * + * @return a {@link Mono} that indicates that the connection has been closed + */ + @Override + Mono close(); + + /** + * {@inheritDoc} + * + * @return a {@link Mono} that indicates that the transaction has been committed + */ + @Override + Mono commitTransaction(); + + /** + * {@inheritDoc} + * + * @return a {@link MySqlBatch} that can be used to execute a batch of statements + */ + @Override + MySqlBatch createBatch(); + + /** + * {@inheritDoc} + * + * @param name the savepoint name, must not be {@code null} + * @return a {@link Mono} that indicates that the savepoint has been created + * @throws IllegalArgumentException if {@code name} is {@code null} + */ + @Override + Mono createSavepoint(String name); + + /** + * {@inheritDoc} + * + * @param sql the SQL to execute, must not be {@code null} + * @return a new {@link MySqlStatement} instance + * @throws IllegalArgumentException if {@code sql} is {@code null} + */ + @Override + MySqlStatement createStatement(String sql); + + /** + * {@inheritDoc} + * + * @return a {@link MySqlConnectionMetadata} that contains the connection metadata + */ + @Override + MySqlConnectionMetadata getMetadata(); + + /** + * {@inheritDoc} + * + * @param name the savepoint name, must not be {@code null} + * @return a {@link Mono} that indicates that the savepoint has been released + * @throws IllegalArgumentException if {@code name} is {@code null} + */ + @Override + Mono releaseSavepoint(String name); + + /** + * {@inheritDoc} + * + * @return a {@link Mono} that indicates that the transaction has been rolled back + */ + @Override + Mono rollbackTransaction(); + + /** + * {@inheritDoc} + * + * @param name the savepoint name, must not be {@code null} + * @return a {@link Mono} that indicates that the transaction has been rolled back to the savepoint + * @throws IllegalArgumentException if {@code name} is {@code null} + */ + @Override + Mono rollbackTransactionToSavepoint(String name); + + /** + * {@inheritDoc} + * + * @param autoCommit the auto-commit mode + * @return a {@link Mono} that indicates that the auto-commit mode has been set + */ + @Override + Mono setAutoCommit(boolean autoCommit); + + /** + * {@inheritDoc} + *

+ * Note: Currently, it should be used only for InnoDB storage engine. + * + * @param timeout the lock wait timeout, must not be {@code null} + * @return a {@link Mono} that indicates that the lock wait timeout has been set + * @throws IllegalArgumentException if {@code timeout} is {@code null} + */ + @Override + Mono setLockWaitTimeout(Duration timeout); + + /** + * {@inheritDoc} + * + * @param timeout the statement timeout, must not be {@code null} + * @return a {@link Mono} that indicates that the statement timeout has been set + * @throws IllegalArgumentException if {@code timeout} is {@code null} + */ + @Override + Mono setStatementTimeout(Duration timeout); + + /** + * {@inheritDoc} + * + * @param isolationLevel the isolation level, must not be {@code null} + * @return a {@link Mono} that indicates that the isolation level of the current session has been set + * @throws IllegalArgumentException if {@code isolationLevel} is {@code null} + */ + @Override + Mono setTransactionIsolationLevel(IsolationLevel isolationLevel); + + /** + * {@inheritDoc} + * + * @param depth the validation depth, must not be {@code null} + * @return a {@link Mono} that indicates that the connection has been validated + * @throws IllegalArgumentException if {@code depth} is {@code null} + */ + @Override + Mono validate(ValidationDepth depth); + + /** + * {@inheritDoc} + * + * @return a {@link Mono} that indicates that the connection is ready for usage + */ + @Override + Mono postAllocate(); + + /** + * {@inheritDoc} + * + * @return a {@link Mono} that indicates that the connection is ready for release + */ + @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..41adc5f7b --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlConnectionMetadata.java @@ -0,0 +1,52 @@ +/* + * 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} + *

+ * Note: it should be the result of {@code SELECT @@version_comment} + * + * @return the product name of the database + */ + @Override + String getDatabaseProductName(); + + /** + * {@inheritDoc} + * + * @return the version received from the server, e.g. {@code 5.7.30}, {@code 5.5.5-10.4.13-MariaDB} + */ + @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..adb9533a3 --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlNativeTypeMetadata.java @@ -0,0 +1,74 @@ +/* + * 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, and some types will use the same + * identifier. e.g. {@code TEXT} and {@code BLOB} are using {@code 252}. + * + * @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/api/MySqlOutParameters.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameters.java new file mode 100644 index 000000000..cd771dce1 --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParameters.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.OutParameters; + +/** + * {@link OutParameters} for a collection of {@code OUT} parameters returned from a MySQL database. + * + * @since 1.1.3 + */ +public interface MySqlOutParameters extends MySqlReadable, OutParameters { + + /** + * {@inheritDoc} + * + * @return the {@link MySqlOutParametersMetadata} for all {@code OUT} parameters + */ + @Override + 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..eee96606c --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlOutParametersMetadata.java @@ -0,0 +1,59 @@ +/* + * 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; +import java.util.NoSuchElementException; + +/** + * {@link OutParametersMetadata} for {@code OUT} parameters metadata returned from a MySQL database. + * + * @since 1.1.3 + */ +public interface MySqlOutParametersMetadata extends OutParametersMetadata { + + /** + * {@inheritDoc} + * + * @param index the out parameter index starting at 0 + * @return the {@link MySqlOutParametersMetadata} for one out parameter + * @throws IndexOutOfBoundsException if {@code index} is out of range + */ + @Override + MySqlOutParameterMetadata getParameterMetadata(int index); + + /** + * {@inheritDoc} + * + * @param name the name of the out parameter. Parameter names are case-insensitive. + * @return the {@link MySqlOutParameterMetadata} for one out parameter + * @throws IllegalArgumentException if {@code name} is {@code null} + * @throws NoSuchElementException if there is no out parameter with the {@code name} + */ + @Override + MySqlOutParameterMetadata getParameterMetadata(String name); + + /** + * {@inheritDoc} + * + * @return the {@link MySqlOutParameterMetadata} for all out parameters + */ + @Override + List 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 61% 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..c8b69b08c 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,45 +14,55 @@ * 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} + * + * @return the {@link MySqlType} descriptor. */ @Override 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. + * {@inheritDoc} * - * @return the field max size. + * @return the {@link MySqlNativeTypeMetadata}. */ - long getNativePrecision(); + @Override + default MySqlNativeTypeMetadata getNativeTypeMetadata() { + return null; + } + /** + * {@inheritDoc} + * + * @return the primary Java {@link Class type}. + * @see MySqlRow#get + * @see MySqlOutParameters#get + */ @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..81fabfeea --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlResult.java @@ -0,0 +1,173 @@ +/* + * 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. + * @throws IllegalStateException if the result was consumed + */ + @Override + Mono getRowsUpdated(); + + /** + * {@inheritDoc} + * + * @param mappingFunction that maps a {@link Row} and {@link RowMetadata} to a value + * @param the type of the mapped value + * @return a {@link Flux} of mapped results + * @throws IllegalArgumentException if {@code mappingFunction} is {@code null} + * @throws IllegalStateException if the result was consumed + */ + @Override + Flux map(BiFunction mappingFunction); + + /** + * {@inheritDoc} + * + * @param mappingFunction that maps a {@link Readable} to a value + * @param the type of the mapped value + * @return a {@link Flux} of mapped results + * @throws IllegalArgumentException if {@code mappingFunction} is {@code null} + * @throws IllegalStateException if the result was consumed + * @see MySqlReadable + * @see MySqlRow + * @see MySqlOutParameters + */ + @Override + Flux map(Function mappingFunction); + + /** + * {@inheritDoc} + * + * @param filter to apply to each element to determine if it should be included + * @return a {@link MySqlResult} that will only emit results that match the {@code predicate} + * @throws IllegalArgumentException if {@code predicate} is {@code null} + * @throws IllegalStateException if the result was consumed + */ + @Override + MySqlResult filter(Predicate filter); + + /** + * {@inheritDoc} + * + * @param mappingFunction that maps a {@link Result.Segment} a to a {@link Publisher} + * @param the type of the mapped value + * @return a {@link Flux} of mapped results + * @throws IllegalArgumentException if {@code mappingFunction} is {@code null} + * @throws IllegalStateException if the result was consumed + */ + @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}. + */ + interface OutSegment extends Segment, Result.OutSegment { + + /** + * Retrieve all {@code OUT} parameters as a {@link MySqlRow}. + *

+ * 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 + * can be retrieved as a {@link MySqlRow}, but not vice versa. + * + * @return a {@link MySqlRow} of all {@code OUT} parameters + */ + MySqlRow row(); + + /** + * Gets all {@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..da1f04a1a --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRow.java @@ -0,0 +1,75 @@ +/* + * 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; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.ParameterizedType; +import java.util.NoSuchElementException; + +/** + * 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(); + + /** + * Returns the value which can be a generic type. + *

+ * UNSTABLE: it is not a standard of {@code r2dbc-spi}, so it may be changed in the future. + * + * @param index the index starting at {@code 0} + * @param type the parameterized type of item to return. + * @param the type of the item being returned. + * @return the value for a column in this row. Value can be {@code null}. + * @throws IllegalArgumentException if {@code name} or {@code type} is {@code null}. + * @throws IndexOutOfBoundsException if {@code index} is out of range + * @throws UnsupportedOperationException if the row is containing last inserted ID + */ + @Nullable + default T get(int index, ParameterizedType type) { + throw new UnsupportedOperationException("Unsupported operation"); + } + + /** + * Returns the value which can be a generic type. + *

+ * UNSTABLE: it is not a standard of {@code r2dbc-spi}, so it may be changed in the future. + * + * @param name the name of the column. + * @param type the parameterized type of item to return. + * @param the type of the item being returned. + * @return the value for a column in this row. Value can be {@code null}. + * @throws IllegalArgumentException if {@code name} or {@code type} is {@code null}. + * @throws NoSuchElementException if {@code name} is not a known readable column or out parameter + * @throws UnsupportedOperationException if the row is containing last inserted ID + */ + @Nullable + default T get(String name, ParameterizedType type) { + throw new UnsupportedOperationException("Unsupported operation"); + } +} 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..c9a67c251 --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlRowMetadata.java @@ -0,0 +1,70 @@ +/* + * 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; +import java.util.NoSuchElementException; + +/** + * {@link RowMetadata} for a row metadata returned from a MySQL database. + * + * @since 1.1.3 + */ +public interface MySqlRowMetadata extends RowMetadata { + + /** + * {@inheritDoc} + * + * @param index the column index starting at 0 + * @return the {@link MySqlRowMetadata} for one column in this row + * @throws IndexOutOfBoundsException if {@code index} is out of range + */ + @Override + MySqlColumnMetadata getColumnMetadata(int index); + + /** + * {@inheritDoc} + * + * @param name the name of the column. Column names are case-insensitive. When a get method contains + * several columns with same name, then the value of the first matching column will be + * returned + * @return the {@link MySqlColumnMetadata} for one column in this row + * @throws IllegalArgumentException if {@code name} is {@code null} + * @throws NoSuchElementException if there is no column with the {@code name} + */ + @Override + MySqlColumnMetadata getColumnMetadata(String name); + + /** + * {@inheritDoc} + * + * @return the {@link MySqlColumnMetadata} for all columns in this row + */ + @Override + List getColumnMetadatas(); + + /** + * {@inheritDoc} + * + * @param columnName the name of the column. Column names are case-insensitive. + * @return {@code true} if this object contains metadata for {@code columnName}; {@code false} otherwise. + */ + @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..a1eff204d --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/MySqlStatement.java @@ -0,0 +1,129 @@ +/* + * 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 java.util.NoSuchElementException; + +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 statement is parametrized and not all parameters are provided + */ + @Override + MySqlStatement add(); + + /** + * {@inheritDoc} + * + * @param index the index to bind to + * @param value the value to bind + * @return {@link MySqlStatement this} + * @throws IllegalArgumentException if {@code value} is {@code null} + * @throws IndexOutOfBoundsException if the parameter {@code index} is out of range + * @throws UnsupportedOperationException if the statement is not a parameterized statement + */ + @Override + MySqlStatement bind(int index, Object value); + + /** + * {@inheritDoc} + * + * @param name the name of identifier to bind to + * @param value the value to bind + * @return {@link MySqlStatement this} + * @throws IllegalArgumentException if {@code name} or {@code value} is {@code null} + * @throws NoSuchElementException if {@code name} is not a known name to bind + * @throws UnsupportedOperationException if the statement is not a parameterized statement + */ + @Override + MySqlStatement bind(String name, Object value); + + /** + * {@inheritDoc} + * + * @param index the index to bind to + * @param type the type of null value + * @return {@link MySqlStatement this} + * @throws IllegalArgumentException if {@code type} is {@code null} + * @throws IndexOutOfBoundsException if the parameter {@code index} is out of range + * @throws UnsupportedOperationException if the statement is not a parameterized statement + */ + @Override + MySqlStatement bindNull(int index, Class type); + + /** + * {@inheritDoc} + * + * @param name the name of identifier to bind to + * @param type the type of null value + * @return {@link MySqlStatement this} + * @throws IllegalArgumentException if {@code name} or {@code type} is {@code null} + * @throws NoSuchElementException if {@code name} is not a known name to bind + * @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 + * @throws IllegalStateException if the statement is parametrized and not all parameters are provided + */ + @Override + Flux execute(); + + /** + * {@inheritDoc} + * + * @param columns the names of the columns to return + * @return {@link MySqlStatement this} + * @throws IllegalArgumentException if {@code columns}, or any item is empty or {@code null} + */ + @Override + default MySqlStatement returnGeneratedValues(String... columns) { + requireNonNull(columns, "columns must not be null"); + return this; + } + + /** + * {@inheritDoc} + * + * @param rows the number of rows to fetch + * @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, Object> options; + + private SimpleTransactionDefinition(Map, Object> options) { + this.options = options; + } + + @SuppressWarnings("unchecked") + @Override + public T getAttribute(Option option) { + return (T) this.options.get(option); + } + + @Override + public MySqlTransactionDefinition isolationLevel(IsolationLevel isolationLevel) { + requireNonNull(isolationLevel, "isolationLevel must not be null"); + + return with(ISOLATION_LEVEL, isolationLevel); + } + + @Override + public MySqlTransactionDefinition withoutIsolationLevel() { + return without(ISOLATION_LEVEL); + } + + @Override + public MySqlTransactionDefinition readOnly() { + return with(READ_ONLY, true); + } + + @Override + public MySqlTransactionDefinition readWrite() { + return with(READ_ONLY, false); + } + + @Override + public MySqlTransactionDefinition withoutMutability() { + return without(READ_ONLY); + } + + @Override + public MySqlTransactionDefinition lockWaitTimeout(Duration timeout) { + requireNonNull(timeout, "timeout must not be null"); + + return with(LOCK_WAIT_TIMEOUT, timeout); + } + + @Override + public MySqlTransactionDefinition withoutLockWaitTimeout() { + return without(LOCK_WAIT_TIMEOUT); + } + + @Override + public MySqlTransactionDefinition consistent() { + return with(WITH_CONSISTENT_SNAPSHOT, true); + } + + @Override + public MySqlTransactionDefinition consistent(String engine) { + requireNonNull(engine, "engine must not be null"); + + return consistent0(CONSISTENT_SNAPSHOT_ENGINE, engine); + } + + @Override + public MySqlTransactionDefinition consistent(String engine, long sessionId) { + requireNonNull(engine, "engine must not be null"); + + Map, Object> options = new HashMap<>(this.options); + + options.put(WITH_CONSISTENT_SNAPSHOT, true); + options.put(CONSISTENT_SNAPSHOT_ENGINE, engine); + options.put(CONSISTENT_SNAPSHOT_FROM_SESSION, sessionId); + + return of(options); + } + + @Override + public MySqlTransactionDefinition consistentFromSession(long sessionId) { + return consistent0(CONSISTENT_SNAPSHOT_FROM_SESSION, sessionId); + } + + @Override + public MySqlTransactionDefinition withoutConsistent() { + if (this.options.isEmpty()) { + return this; + } + + Map, Object> options = new HashMap<>(this.options); + + options.remove(WITH_CONSISTENT_SNAPSHOT); + options.remove(CONSISTENT_SNAPSHOT_ENGINE); + options.remove(CONSISTENT_SNAPSHOT_FROM_SESSION); + + return of(options); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SimpleTransactionDefinition)) { + return false; + } + + SimpleTransactionDefinition that = (SimpleTransactionDefinition) o; + + return options.equals(that.options); + } + + @Override + public int hashCode() { + return options.hashCode(); + } + + @Override + public String toString() { + return "MySqlTransactionDefinition" + options; + } + + private MySqlTransactionDefinition with(Option option, T value) { + if (this.options.isEmpty()) { + return new SimpleTransactionDefinition(Collections.singletonMap(option, value)); + } + + if (value.equals(this.options.get(option))) { + return this; + } + + Map, Object> options = new HashMap<>(this.options); + + options.put(option, value); + + return of(options); + } + + private SimpleTransactionDefinition without(Option option) { + requireNonNull(option, "option must not be null"); + + if (!this.options.containsKey(option)) { + return this; + } + + if (this.options.size() == 1) { + return EMPTY; + } + + Map, Object> options = new HashMap<>(this.options); + + options.remove(option); + + return of(options); + } + + private MySqlTransactionDefinition consistent0(Option option, T value) { + if (Boolean.TRUE.equals(this.options.get(WITH_CONSISTENT_SNAPSHOT))) { + return with(option, value); + } + + Map, Object> options = new HashMap<>(this.options); + + options.put(WITH_CONSISTENT_SNAPSHOT, true); + options.put(option, value); + + return of(options); + } + + private static SimpleTransactionDefinition of(Map, Object> options) { + switch (options.size()) { + case 0: + return EMPTY; + case 1: { + Map.Entry, Object> e = options.entrySet().iterator().next(); + + return new SimpleTransactionDefinition(Collections.singletonMap(e.getKey(), e.getValue())); + } + default: + return new SimpleTransactionDefinition(options); + } + } +} diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/package-info.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/package-info.java new file mode 100644 index 000000000..65e67fa90 --- /dev/null +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/api/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * R2DBC driver API for MySQL. + */ + +@NotNullByDefault +package io.asyncer.r2dbc.mysql.api; + +import io.asyncer.r2dbc.mysql.internal.NotNullByDefault; diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractClassedCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractClassedCodec.java index 9e26692fb..2462c46dc 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractClassedCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractClassedCodec.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; /** * Codec for classed type when field bytes less or equals than {@link Integer#MAX_VALUE}. @@ -32,9 +32,9 @@ abstract class AbstractClassedCodec implements Codec { } @Override - public final boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public final boolean canDecode(MySqlReadableMetadata metadata, Class target) { return target.isAssignableFrom(this.type) && doCanDecode(metadata); } - protected abstract boolean doCanDecode(MySqlColumnMetadata metadata); + protected abstract boolean doCanDecode(MySqlReadableMetadata metadata); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractPrimitiveCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractPrimitiveCodec.java index 3370d79d0..295b0d18d 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractPrimitiveCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/AbstractPrimitiveCodec.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import static io.asyncer.r2dbc.mysql.internal.util.AssertUtils.require; @@ -40,7 +40,7 @@ abstract class AbstractPrimitiveCodec implements PrimitiveCodec { } @Override - public final boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public final boolean canDecode(MySqlReadableMetadata metadata, Class target) { return target.isAssignableFrom(boxedClass) && canPrimitiveDecode(metadata); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigDecimalCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigDecimalCodec.java index 7d7e7008c..ced23b056 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigDecimalCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigDecimalCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -39,7 +39,7 @@ private BigDecimalCodec() { } @Override - public BigDecimal decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public BigDecimal decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { MySqlType type = metadata.getType(); @@ -82,7 +82,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - protected boolean doCanDecode(MySqlColumnMetadata metadata) { + protected boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigIntegerCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigIntegerCodec.java index 0b1be61f4..622c9edf0 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigIntegerCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BigIntegerCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -40,7 +40,7 @@ private BigIntegerCodec() { } @Override - public BigInteger decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public BigInteger decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { MySqlType type = metadata.getType(); @@ -93,7 +93,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - protected boolean doCanDecode(MySqlColumnMetadata metadata) { + protected boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BitSetCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BitSetCodec.java index 839c81a6e..7ad97b1fb 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BitSetCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BitSetCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -41,7 +41,7 @@ private BitSetCodec() { } @Override - public BitSet decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public BitSet decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { if (!value.isReadable()) { return BitSet.valueOf(EMPTY_BYTES); @@ -91,7 +91,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - protected boolean doCanDecode(MySqlColumnMetadata metadata) { + protected boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType() == MySqlType.BIT; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BlobCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BlobCodec.java index 0363986e3..dc3a75b5e 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BlobCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BlobCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.codec.lob.LobUtils; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; @@ -50,19 +50,19 @@ private BlobCodec() { } @Override - public Blob decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Blob decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return LobUtils.createBlob(value); } @Override - public Blob decodeMassive(List value, MySqlColumnMetadata metadata, Class target, + public Blob decodeMassive(List value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return LobUtils.createBlob(value); } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { MySqlType type = metadata.getType(); return (type.isLob() || type == MySqlType.GEOMETRY) && target.isAssignableFrom(Blob.class); diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java index adec25731..3b8035c5f 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/BooleanCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -36,7 +36,7 @@ private BooleanCodec() { } @Override - public Boolean decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Boolean decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return binary || metadata.getType() == MySqlType.BIT ? value.readBoolean() : value.readByte() != '0'; } @@ -52,9 +52,10 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canPrimitiveDecode(MySqlColumnMetadata metadata) { + public boolean canPrimitiveDecode(MySqlReadableMetadata metadata) { MySqlType type = metadata.getType(); - return (type == MySqlType.BIT || type == MySqlType.TINYINT) && metadata.getNativePrecision() == 1; + return (type == MySqlType.BIT || type == MySqlType.TINYINT) && + Integer.valueOf(1).equals(metadata.getPrecision()); } private static final class BooleanMySqlParameter extends AbstractMySqlParameter { diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayCodec.java index aa4beb2fb..4dd9b989c 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteArrayCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; import io.netty.buffer.ByteBuf; @@ -42,7 +42,7 @@ private ByteArrayCodec() { } @Override - public byte[] decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public byte[] decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { if (!value.isReadable()) { return EMPTY_BYTES; @@ -62,7 +62,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - protected boolean doCanDecode(MySqlColumnMetadata metadata) { + protected boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType().isBinary(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteBufferCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteBufferCodec.java index 0720e6c81..598551d29 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteBufferCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteBufferCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; import io.netty.buffer.ByteBuf; @@ -41,7 +41,7 @@ private ByteBufferCodec() { } @Override - public ByteBuffer decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public ByteBuffer decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { if (!value.isReadable()) { return ByteBuffer.wrap(EMPTY_BYTES); @@ -66,7 +66,7 @@ public boolean canEncode(Object value) { } @Override - protected boolean doCanDecode(MySqlColumnMetadata metadata) { + protected boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType().isBinary(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteCodec.java index c8257cf3d..649a6266d 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ByteCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -36,7 +36,7 @@ private ByteCodec() { } @Override - public Byte decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Byte decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return (byte) IntegerCodec.decodeInt(value, binary, metadata.getType()); } @@ -52,7 +52,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canPrimitiveDecode(MySqlColumnMetadata metadata) { + public boolean canPrimitiveDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ClobCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ClobCodec.java index 9d69a0c57..b3bae3689 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ClobCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ClobCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.codec.lob.LobUtils; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; @@ -53,19 +53,19 @@ private ClobCodec() { } @Override - public Clob decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Clob decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return LobUtils.createClob(value, metadata.getCharCollation(context)); } @Override - public Clob decodeMassive(List value, MySqlColumnMetadata metadata, Class target, + public Clob decodeMassive(List value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return LobUtils.createClob(value, metadata.getCharCollation(context)); } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { MySqlType type = metadata.getType(); return (type.isLob() || type == MySqlType.JSON) && target.isAssignableFrom(Clob.class); diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codec.java index eb184fc20..c970c212f 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codec.java @@ -16,8 +16,8 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.netty.buffer.ByteBuf; import org.jetbrains.annotations.Nullable; @@ -34,24 +34,24 @@ public interface Codec { * Decodes a {@link ByteBuf} as specified {@link Class}. * * @param value the {@link ByteBuf}. - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param target the specified {@link Class}. * @param binary if the value should be decoded by binary protocol. * @param context the codec context. * @return the decoded result. */ @Nullable - T decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + T decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context); /** * Checks if the field value can be decoded as specified {@link Class}. * - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param target the specified {@link Class}. * @return if it can decode. */ - boolean canDecode(MySqlColumnMetadata metadata, Class target); + boolean canDecode(MySqlReadableMetadata metadata, Class target); /** * Checks if it can encode the specified value. diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codecs.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codecs.java index 45129a26f..d9ac71e99 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codecs.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/Codecs.java @@ -16,8 +16,8 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.message.FieldValue; import io.netty.buffer.ByteBufAllocator; import org.jetbrains.annotations.Nullable; @@ -33,7 +33,7 @@ public interface Codecs { * Decode a {@link FieldValue} as specified {@link Class type}. * * @param value the {@link FieldValue}. - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param type the specified {@link Class}. * @param binary if the value should be decoded by binary protocol. * @param context the codec context. @@ -42,14 +42,14 @@ public interface Codecs { * @throws IllegalArgumentException if any parameter is {@code null}, or {@code value} cannot be decoded. */ @Nullable - T decode(FieldValue value, MySqlColumnMetadata metadata, Class type, boolean binary, + T decode(FieldValue value, MySqlReadableMetadata metadata, Class type, boolean binary, CodecContext context); /** * Decode a {@link FieldValue} as a specified {@link ParameterizedType type}. * * @param value the {@link FieldValue}. - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param type the specified {@link ParameterizedType}. * @param binary if the value should be decoded by binary protocol. * @param context the codec context. @@ -58,7 +58,7 @@ T decode(FieldValue value, MySqlColumnMetadata metadata, Class type, bool * @throws IllegalArgumentException if any parameter is {@code null}, or {@code value} cannot be decoded. */ @Nullable - T decode(FieldValue value, MySqlColumnMetadata metadata, ParameterizedType type, boolean binary, + T decode(FieldValue value, MySqlReadableMetadata metadata, ParameterizedType type, boolean binary, CodecContext context); /** 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 9c95d7161..882637f10 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 @@ -16,8 +16,8 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.internal.util.InternalArrays; import io.asyncer.r2dbc.mysql.message.FieldValue; import io.asyncer.r2dbc.mysql.message.LargeFieldValue; @@ -89,7 +89,7 @@ private DefaultCodecs(Codec[] codecs) { * release this buffer. */ @Override - public T decode(FieldValue value, MySqlColumnMetadata metadata, Class type, boolean binary, + public T decode(FieldValue value, MySqlReadableMetadata metadata, Class type, boolean binary, CodecContext context) { requireNonNull(value, "value must not be null"); requireNonNull(metadata, "info must not be null"); @@ -117,7 +117,7 @@ public T decode(FieldValue value, MySqlColumnMetadata metadata, Class typ } @Override - public T decode(FieldValue value, MySqlColumnMetadata metadata, ParameterizedType type, + public T decode(FieldValue value, MySqlReadableMetadata metadata, ParameterizedType type, boolean binary, CodecContext context) { requireNonNull(value, "value must not be null"); requireNonNull(metadata, "info must not be null"); @@ -199,7 +199,7 @@ public MySqlParameter encodeNull() { } @Nullable - private T decodePrimitive(FieldValue value, MySqlColumnMetadata metadata, Class type, + private T decodePrimitive(FieldValue value, MySqlReadableMetadata metadata, Class type, boolean binary, CodecContext context) { @SuppressWarnings("unchecked") PrimitiveCodec codec = (PrimitiveCodec) this.primitiveCodecs.get(type); @@ -214,7 +214,7 @@ private T decodePrimitive(FieldValue value, MySqlColumnMetadata metadata, Cl } @Nullable - private T decodeNormal(NormalFieldValue value, MySqlColumnMetadata metadata, Class type, + private T decodeNormal(NormalFieldValue value, MySqlReadableMetadata metadata, Class type, boolean binary, CodecContext context) { for (Codec codec : codecs) { if (codec.canDecode(metadata, type)) { @@ -228,7 +228,7 @@ private T decodeNormal(NormalFieldValue value, MySqlColumnMetadata metadata, } @Nullable - private T decodeNormal(NormalFieldValue value, MySqlColumnMetadata metadata, ParameterizedType type, + private T decodeNormal(NormalFieldValue value, MySqlReadableMetadata metadata, ParameterizedType type, boolean binary, CodecContext context) { for (ParametrizedCodec codec : parametrizedCodecs) { if (codec.canDecode(metadata, type)) { @@ -242,7 +242,7 @@ private T decodeNormal(NormalFieldValue value, MySqlColumnMetadata metadata, } @Nullable - private T decodeMassive(LargeFieldValue value, MySqlColumnMetadata metadata, Class type, + private T decodeMassive(LargeFieldValue value, MySqlReadableMetadata metadata, Class type, boolean binary, CodecContext context) { for (MassiveCodec codec : massiveCodecs) { if (codec.canDecode(metadata, type)) { @@ -256,7 +256,7 @@ private T decodeMassive(LargeFieldValue value, MySqlColumnMetadata metadata, } @Nullable - private T decodeMassive(LargeFieldValue value, MySqlColumnMetadata metadata, ParameterizedType type, + private T decodeMassive(LargeFieldValue value, MySqlReadableMetadata metadata, ParameterizedType type, boolean binary, CodecContext context) { for (MassiveParametrizedCodec codec : massiveParametrizedCodecs) { if (codec.canDecode(metadata, type)) { @@ -269,7 +269,7 @@ private T decodeMassive(LargeFieldValue value, MySqlColumnMetadata metadata, throw new IllegalArgumentException("Cannot decode massive " + type + " for " + metadata.getType()); } - private static Class chooseClass(MySqlColumnMetadata metadata, Class type) { + private static Class chooseClass(MySqlReadableMetadata metadata, Class type) { Class javaType = metadata.getType().getJavaType(); return type.isAssignableFrom(javaType) ? javaType : type; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DoubleCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DoubleCodec.java index 728838787..fa544965b 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DoubleCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DoubleCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -38,7 +38,7 @@ private DoubleCodec() { } @Override - public Double decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Double decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { MySqlType type = metadata.getType(); @@ -68,7 +68,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canPrimitiveDecode(MySqlColumnMetadata metadata) { + public boolean canPrimitiveDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DurationCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DurationCodec.java index e4ae98b87..1c83d06b6 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DurationCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/DurationCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -48,7 +48,7 @@ private DurationCodec() { } @Override - public Duration decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Duration decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return binary ? decodeBinary(value) : decodeText(value); } @@ -64,7 +64,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - protected boolean doCanDecode(MySqlColumnMetadata metadata) { + protected boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType() == MySqlType.TIME; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/EnumCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/EnumCodec.java index 9a2a0ce78..a658b3598 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/EnumCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/EnumCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -38,7 +38,7 @@ private EnumCodec() { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - public Enum decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Enum decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { Charset charset = metadata.getCharCollation(context).getCharset(); @@ -46,7 +46,7 @@ public Enum decode(ByteBuf value, MySqlColumnMetadata metadata, Class targ } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { return metadata.getType() == MySqlType.ENUM && target.isEnum(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/FloatCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/FloatCodec.java index 581b289f8..91dd20b46 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/FloatCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/FloatCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -38,7 +38,7 @@ private FloatCodec() { } @Override - public Float decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Float decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { MySqlType type = metadata.getType(); @@ -68,7 +68,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canPrimitiveDecode(MySqlColumnMetadata metadata) { + public boolean canPrimitiveDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/InstantCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/InstantCodec.java index 17d0793ed..0a14c6ee3 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/InstantCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/InstantCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -40,7 +40,7 @@ private InstantCodec() { } @Override - public Instant decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Instant decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { LocalDateTime origin = LocalDateTimeCodec.decodeOrigin(value, binary, context); @@ -65,7 +65,7 @@ public boolean canEncode(Object value) { } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { return DateTimes.canDecodeDateTime(metadata.getType(), target, Instant.class); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/IntegerCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/IntegerCodec.java index 7e67e2a6f..73b9d702c 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/IntegerCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/IntegerCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.codec.ByteCodec.ByteMySqlParameter; import io.asyncer.r2dbc.mysql.codec.ShortCodec.ShortMySqlParameter; import io.asyncer.r2dbc.mysql.constant.MySqlType; @@ -41,7 +41,7 @@ private IntegerCodec() { } @Override - public Integer decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Integer decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return decodeInt(value, binary, metadata.getType()); } @@ -65,7 +65,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canPrimitiveDecode(MySqlColumnMetadata metadata) { + public boolean canPrimitiveDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateCodec.java index 8f72fcb92..a3f9fbbd3 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -41,7 +41,7 @@ final class LocalDateCodec extends AbstractClassedCodec { } @Override - public LocalDate decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public LocalDate decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { int bytes = value.readableBytes(); LocalDate date = binary ? readDateBinary(value, bytes) : readDateText(value); @@ -64,7 +64,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean doCanDecode(MySqlColumnMetadata metadata) { + public boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType() == MySqlType.DATE; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateTimeCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateTimeCodec.java index a0d553d67..17b09b276 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateTimeCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalDateTimeCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -46,13 +46,13 @@ private LocalDateTimeCodec() { } @Override - public LocalDateTime decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public LocalDateTime decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return decodeOrigin(value, binary, context); } @Override - public ChronoLocalDateTime decode(ByteBuf value, MySqlColumnMetadata metadata, + public ChronoLocalDateTime decode(ByteBuf value, MySqlReadableMetadata metadata, ParameterizedType target, boolean binary, CodecContext context) { return decodeOrigin(value, binary, context); } @@ -68,12 +68,12 @@ public boolean canEncode(Object value) { } @Override - public boolean canDecode(MySqlColumnMetadata metadata, ParameterizedType target) { + public boolean canDecode(MySqlReadableMetadata metadata, ParameterizedType target) { return DateTimes.canDecodeChronology(metadata.getType(), target, ChronoLocalDateTime.class); } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { return DateTimes.canDecodeDateTime(metadata.getType(), target, LocalDateTime.class); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalTimeCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalTimeCodec.java index 101f88af6..c0102709b 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalTimeCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LocalTimeCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -55,7 +55,7 @@ private LocalTimeCodec() { } @Override - public LocalTime decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public LocalTime decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return decodeOrigin(binary, value); } @@ -71,7 +71,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean doCanDecode(MySqlColumnMetadata metadata) { + public boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType() == MySqlType.TIME; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LongCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LongCodec.java index 862a8f2ce..30c495db6 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LongCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/LongCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.codec.ByteCodec.ByteMySqlParameter; import io.asyncer.r2dbc.mysql.codec.IntegerCodec.IntMySqlParameter; import io.asyncer.r2dbc.mysql.codec.ShortCodec.ShortMySqlParameter; @@ -42,7 +42,7 @@ private LongCodec() { } @Override - public Long decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Long decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { MySqlType type = metadata.getType(); @@ -73,7 +73,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canPrimitiveDecode(MySqlColumnMetadata metadata) { + public boolean canPrimitiveDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveCodec.java index 18bdb80a2..65cbd2222 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveCodec.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.netty.buffer.ByteBuf; import org.jetbrains.annotations.Nullable; @@ -33,13 +33,13 @@ public interface MassiveCodec extends Codec { * Decode a massive value as specified {@link Class}. * * @param value {@link ByteBuf}s list. - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param target the specified {@link Class}. * @param binary if the value should be decoded by binary protocol. * @param context the codec context. * @return the decoded result. */ @Nullable - T decodeMassive(List value, MySqlColumnMetadata metadata, Class target, boolean binary, + T decodeMassive(List value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveParametrizedCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveParametrizedCodec.java index 290a4b815..e43af83d8 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveParametrizedCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/MassiveParametrizedCodec.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.netty.buffer.ByteBuf; import org.jetbrains.annotations.Nullable; @@ -34,14 +34,13 @@ public interface MassiveParametrizedCodec extends ParametrizedCodec, Massi * Decode a massive value as specified {@link ParameterizedType}. * * @param value {@link ByteBuf}s list. - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param target the specified {@link ParameterizedType}. * @param binary if the value should be decoded by binary protocol. * @param context the codec context. * @return the decoded result. */ @Nullable - Object decodeMassive(List value, MySqlColumnMetadata metadata, ParameterizedType target, - boolean binary, - CodecContext context); + Object decodeMassive(List value, MySqlReadableMetadata metadata, ParameterizedType target, + boolean binary, CodecContext context); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetDateTimeCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetDateTimeCodec.java index 694578b13..b99714638 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetDateTimeCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetDateTimeCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -40,7 +40,7 @@ private OffsetDateTimeCodec() { } @Override - public OffsetDateTime decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public OffsetDateTime decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { LocalDateTime origin = LocalDateTimeCodec.decodeOrigin(value, binary, context); @@ -65,7 +65,7 @@ public boolean canEncode(Object value) { } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { return DateTimes.canDecodeDateTime(metadata.getType(), target, OffsetDateTime.class); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetTimeCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetTimeCodec.java index 1ed3769a6..57fb77b17 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetTimeCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/OffsetTimeCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -42,7 +42,7 @@ private OffsetTimeCodec() { } @Override - public OffsetTime decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public OffsetTime decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { // OffsetTime is not an instant value, so preserveInstants is not used here. LocalTime origin = LocalTimeCodec.decodeOrigin(binary, value); @@ -63,7 +63,7 @@ public boolean canEncode(Object value) { } @Override - public boolean doCanDecode(MySqlColumnMetadata metadata) { + public boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType() == MySqlType.TIME; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ParametrizedCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ParametrizedCodec.java index 9c43bedb4..2950b08bb 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ParametrizedCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ParametrizedCodec.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.netty.buffer.ByteBuf; import org.jetbrains.annotations.Nullable; @@ -35,22 +35,22 @@ public interface ParametrizedCodec extends Codec { * Decodes a {@link ByteBuf} as specified {@link ParameterizedType}. * * @param value the {@link ByteBuf}. - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param target the specified {@link ParameterizedType}. * @param binary if the value should be decoded by binary protocol. * @param context the codec context. * @return the decoded result. */ @Nullable - Object decode(ByteBuf value, MySqlColumnMetadata metadata, ParameterizedType target, boolean binary, + Object decode(ByteBuf value, MySqlReadableMetadata metadata, ParameterizedType target, boolean binary, CodecContext context); /** * Checks if the field value can be decoded as specified {@link ParameterizedType}. * - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param target the specified {@link ParameterizedType}. * @return if it can decode. */ - boolean canDecode(MySqlColumnMetadata metadata, ParameterizedType target); + boolean canDecode(MySqlReadableMetadata metadata, ParameterizedType target); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/PrimitiveCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/PrimitiveCodec.java index 3e2c90691..b8c420777 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/PrimitiveCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/PrimitiveCodec.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.netty.buffer.ByteBuf; /** @@ -33,23 +33,23 @@ interface PrimitiveCodec extends Codec { * Decodes a {@link ByteBuf} as specified {@link Class}. * * @param value the {@link ByteBuf}. - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @param target the specified {@link Class}, which can be a primitive type. * @param binary if the value should be decoded by binary protocol. * @param context the codec context. * @return the decoded data that is boxed. */ @Override - T decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + T decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context); /** * Checks if the field value can be decoded as a primitive data. * - * @param metadata the metadata of the column. + * @param metadata the metadata of the column or the {@code OUT} parameter. * @return if it can decode. */ - boolean canPrimitiveDecode(MySqlColumnMetadata metadata); + boolean canPrimitiveDecode(MySqlReadableMetadata metadata); /** * Gets the primitive {@link Class}, such as {@link Integer#TYPE}, etc. diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/SetCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/SetCodec.java index 67e8db371..a4ce64e06 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/SetCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/SetCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.InternalArrays; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; @@ -49,7 +49,7 @@ private SetCodec() { } @Override - public String[] decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public String[] decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { if (!value.isReadable()) { return EMPTY_STRINGS; @@ -67,7 +67,7 @@ public String[] decode(ByteBuf value, MySqlColumnMetadata metadata, Class tar @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - public Set decode(ByteBuf value, MySqlColumnMetadata metadata, ParameterizedType target, boolean binary, + public Set decode(ByteBuf value, MySqlReadableMetadata metadata, ParameterizedType target, boolean binary, CodecContext context) { if (!value.isReadable()) { return Collections.emptySet(); @@ -105,12 +105,12 @@ public Set decode(ByteBuf value, MySqlColumnMetadata metadata, ParameterizedT } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { return metadata.getType() == MySqlType.SET && target.isAssignableFrom(String[].class); } @Override - public boolean canDecode(MySqlColumnMetadata metadata, ParameterizedType target) { + public boolean canDecode(MySqlReadableMetadata metadata, ParameterizedType target) { if (metadata.getType() != MySqlType.SET) { return false; } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ShortCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ShortCodec.java index 952ce2de7..c3c42948c 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ShortCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ShortCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.codec.ByteCodec.ByteMySqlParameter; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; @@ -37,7 +37,7 @@ private ShortCodec() { } @Override - public Short decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Short decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return (short) IntegerCodec.decodeInt(value, binary, metadata.getType()); } @@ -59,7 +59,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canPrimitiveDecode(MySqlColumnMetadata metadata) { + public boolean canPrimitiveDecode(MySqlReadableMetadata metadata) { return metadata.getType().isNumeric(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/StringCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/StringCodec.java index cb5a661bb..2a4bfe5a7 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/StringCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/StringCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; import io.netty.buffer.ByteBuf; @@ -39,7 +39,7 @@ private StringCodec() { } @Override - public String decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public String decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { if (!value.isReadable()) { return ""; @@ -59,7 +59,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - protected boolean doCanDecode(MySqlColumnMetadata metadata) { + protected boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType().isString(); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/YearCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/YearCodec.java index 90a54c1bb..fa7f8b9c0 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/YearCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/YearCodec.java @@ -16,8 +16,8 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.codec.ByteCodec.ByteMySqlParameter; import io.asyncer.r2dbc.mysql.codec.IntegerCodec.IntMySqlParameter; import io.asyncer.r2dbc.mysql.codec.ShortCodec.ShortMySqlParameter; @@ -40,7 +40,7 @@ private YearCodec() { } @Override - public Year decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Year decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return binary ? Year.of(value.readShortLE()) : Year.of(CodecUtils.parseInt(value)); } @@ -66,7 +66,7 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean doCanDecode(MySqlColumnMetadata metadata) { + public boolean doCanDecode(MySqlReadableMetadata metadata) { return metadata.getType() == MySqlType.YEAR; } } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ZonedDateTimeCodec.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ZonedDateTimeCodec.java index 3bd0072b6..9af4aaa61 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ZonedDateTimeCodec.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/codec/ZonedDateTimeCodec.java @@ -16,9 +16,9 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -45,13 +45,13 @@ private ZonedDateTimeCodec() { } @Override - public ZonedDateTime decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public ZonedDateTime decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { return decode0(value, binary, context); } @Override - public ChronoZonedDateTime decode(ByteBuf value, MySqlColumnMetadata metadata, + public ChronoZonedDateTime decode(ByteBuf value, MySqlReadableMetadata metadata, ParameterizedType target, boolean binary, CodecContext context) { return decode0(value, binary, context); } @@ -67,12 +67,12 @@ public boolean canEncode(Object value) { } @Override - public boolean canDecode(MySqlColumnMetadata metadata, ParameterizedType target) { + public boolean canDecode(MySqlReadableMetadata metadata, ParameterizedType target) { return DateTimes.canDecodeChronology(metadata.getType(), target, ChronoZonedDateTime.class); } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { return DateTimes.canDecodeDateTime(metadata.getType(), target, ZonedDateTime.class); } diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/constant/MySqlType.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/constant/MySqlType.java index d425cd9cd..2485d897b 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/constant/MySqlType.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/constant/MySqlType.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.constant; -import io.asyncer.r2dbc.mysql.ColumnDefinition; +import io.asyncer.r2dbc.mysql.api.MySqlNativeTypeMetadata; import io.r2dbc.spi.Type; import java.math.BigDecimal; @@ -671,24 +671,24 @@ public int getBinarySize() { return 0; } - public static MySqlType of(int id, ColumnDefinition definition) { + public static MySqlType of(MySqlNativeTypeMetadata metadata) { // Maybe need to check if it is a string-like type? - if (definition.isSet()) { + if (metadata.isSet()) { return SET; - } else if (definition.isEnum()) { + } else if (metadata.isEnum()) { return ENUM; } - switch (id) { + switch (metadata.getTypeId()) { case ID_DECIMAL: case ID_NEW_DECIMAL: return DECIMAL; case ID_TINYINT: - return definition.isUnsigned() ? TINYINT_UNSIGNED : TINYINT; + return metadata.isUnsigned() ? TINYINT_UNSIGNED : TINYINT; case ID_SMALLINT: - return definition.isUnsigned() ? SMALLINT_UNSIGNED : SMALLINT; + return metadata.isUnsigned() ? SMALLINT_UNSIGNED : SMALLINT; case ID_INT: - return definition.isUnsigned() ? INT_UNSIGNED : INT; + return metadata.isUnsigned() ? INT_UNSIGNED : INT; case ID_FLOAT: return FLOAT; case ID_DOUBLE: @@ -698,9 +698,9 @@ public static MySqlType of(int id, ColumnDefinition definition) { case ID_TIMESTAMP: return TIMESTAMP; case ID_BIGINT: - return definition.isUnsigned() ? BIGINT_UNSIGNED : BIGINT; + return metadata.isUnsigned() ? BIGINT_UNSIGNED : BIGINT; case ID_MEDIUMINT: - return definition.isUnsigned() ? MEDIUMINT_UNSIGNED : MEDIUMINT; + return metadata.isUnsigned() ? MEDIUMINT_UNSIGNED : MEDIUMINT; case ID_DATE: return DATE; case ID_TIME: @@ -712,7 +712,7 @@ public static MySqlType of(int id, ColumnDefinition definition) { case ID_VARCHAR: case ID_VAR_STRING: case ID_STRING: - return definition.isBinary() ? VARBINARY : VARCHAR; + return metadata.isBinary() ? VARBINARY : VARCHAR; case ID_BIT: return BIT; case ID_JSON: @@ -722,13 +722,13 @@ public static MySqlType of(int id, ColumnDefinition definition) { case ID_SET: return SET; case ID_TINYBLOB: - return definition.isBinary() ? TINYBLOB : TINYTEXT; + return metadata.isBinary() ? TINYBLOB : TINYTEXT; case ID_MEDIUMBLOB: - return definition.isBinary() ? MEDIUMBLOB : MEDIUMTEXT; + return metadata.isBinary() ? MEDIUMBLOB : MEDIUMTEXT; case ID_LONGBLOB: - return definition.isBinary() ? LONGBLOB : LONGTEXT; + return metadata.isBinary() ? LONGBLOB : LONGTEXT; case ID_BLOB: - return definition.isBinary() ? BLOB : TEXT; + return metadata.isBinary() ? BLOB : TEXT; case ID_GEOMETRY: // Most Geometry libraries were using byte[] to encode/decode which based on WKT // (includes Extended-WKT) or WKB diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/DefinitionMetadataMessage.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/DefinitionMetadataMessage.java index a2aac7a8b..b4a9d9fdd 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/DefinitionMetadataMessage.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/DefinitionMetadataMessage.java @@ -16,7 +16,6 @@ package io.asyncer.r2dbc.mysql.message.server; -import io.asyncer.r2dbc.mysql.ColumnDefinition; import io.asyncer.r2dbc.mysql.ConnectionContext; import io.asyncer.r2dbc.mysql.collation.CharCollation; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; @@ -53,15 +52,14 @@ public final class DefinitionMetadataMessage implements ServerMessage { private final short typeId; - private final ColumnDefinition definition; + private final int definitions; private final short decimals; private DefinitionMetadataMessage(@Nullable String database, String table, @Nullable String originTable, String column, @Nullable String originColumn, int collationId, long size, short typeId, - ColumnDefinition definition, short decimals) { + int definitions, short decimals) { require(size >= 0, "size must not be a negative integer"); - require(collationId > 0, "collationId must be a positive integer"); this.database = database; this.table = requireNonNull(table, "table must not be null"); @@ -71,7 +69,7 @@ private DefinitionMetadataMessage(@Nullable String database, String table, @Null this.collationId = collationId; this.size = size; this.typeId = typeId; - this.definition = requireNonNull(definition, "definition must not be null"); + this.definitions = definitions; this.decimals = decimals; } @@ -91,8 +89,8 @@ public short getTypeId() { return typeId; } - public ColumnDefinition getDefinition() { - return definition; + public int getDefinitions() { + return definitions; } public short getDecimals() { @@ -111,7 +109,7 @@ public boolean equals(Object o) { return collationId == that.collationId && size == that.size && typeId == that.typeId && - definition.equals(that.definition) && + definitions == that.definitions && decimals == that.decimals && Objects.equals(database, that.database) && table.equals(that.table) && @@ -123,14 +121,14 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(database, table, originTable, column, originColumn, collationId, size, typeId, - definition, decimals); + definitions, decimals); } @Override public String toString() { return "DefinitionMetadataMessage{database='" + database + "', table='" + table + "' (origin:'" + originTable + "'), column='" + column + "' (origin:'" + originColumn + "'), collationId=" + - collationId + ", size=" + size + ", type=" + typeId + ", definition=" + definition + + collationId + ", size=" + size + ", type=" + typeId + ", definitions=" + definitions + ", decimals=" + decimals + '}'; } @@ -155,11 +153,11 @@ private static DefinitionMetadataMessage decode320(ByteBuf buf, ConnectionContex short typeId = buf.readUnsignedByte(); buf.skipBytes(1); // Constant 0x3 - ColumnDefinition definition = ColumnDefinition.of(buf.readShortLE()); + int definitions = buf.readUnsignedShortLE(); short decimals = buf.readUnsignedByte(); - return new DefinitionMetadataMessage(null, table, null, column, null, collation.getId(), size, typeId, - definition, decimals); + return new DefinitionMetadataMessage(null, table, null, column, null, 0, size, typeId, + definitions, decimals); } private static DefinitionMetadataMessage decode41(ByteBuf buf, ConnectionContext context) { @@ -179,10 +177,10 @@ private static DefinitionMetadataMessage decode41(ByteBuf buf, ConnectionContext int collationId = buf.readUnsignedShortLE(); long size = buf.readUnsignedIntLE(); short typeId = buf.readUnsignedByte(); - ColumnDefinition definition = ColumnDefinition.of(buf.readShortLE(), collationId); + int definitions = buf.readUnsignedShortLE(); return new DefinitionMetadataMessage(database, table, originTable, column, originColumn, collationId, - size, typeId, definition, buf.readUnsignedByte()); + size, typeId, definitions, buf.readUnsignedByte()); } private static String readVarIntSizedString(ByteBuf buf, Charset charset) { diff --git a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/RowMessage.java b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/RowMessage.java index 9e4a3ac74..e914b440b 100644 --- a/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/RowMessage.java +++ b/r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/message/server/RowMessage.java @@ -16,7 +16,7 @@ package io.asyncer.r2dbc.mysql.message.server; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.internal.util.NettyBufferUtils; import io.asyncer.r2dbc.mysql.message.FieldValue; import io.netty.util.ReferenceCounted; @@ -45,7 +45,7 @@ public final class RowMessage implements ReferenceCounted, ServerMessage { * @param context information context array. * @return the {@link FieldValue} array. */ - public FieldValue[] decode(boolean isBinary, MySqlColumnMetadata[] context) { + public FieldValue[] decode(boolean isBinary, MySqlReadableMetadata[] context) { return isBinary ? binary(context) : text(context.length); } @@ -69,7 +69,7 @@ private FieldValue[] text(int size) { } } - private FieldValue[] binary(MySqlColumnMetadata[] context) { + private FieldValue[] binary(MySqlReadableMetadata[] context) { reader.skipOneByte(); // constant 0x00 int size = context.length; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ColumnDefinitionTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ColumnDefinitionTest.java deleted file mode 100644 index 4b84d09ba..000000000 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ColumnDefinitionTest.java +++ /dev/null @@ -1,61 +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; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ColumnDefinition}. - */ -class ColumnDefinitionTest { - - @Test - void allSet() { - ColumnDefinition definition = ColumnDefinition.of(-1); - - assertThat(definition.isBinary()).isTrue(); - assertThat(definition.isSet()).isTrue(); - assertThat(definition.isUnsigned()).isTrue(); - assertThat(definition.isEnum()).isTrue(); - assertThat(definition.isNotNull()).isTrue(); - } - - @Test - void noSet() { - ColumnDefinition definition = ColumnDefinition.of(0); - - assertThat(definition.isBinary()).isFalse(); - assertThat(definition.isSet()).isFalse(); - assertThat(definition.isUnsigned()).isFalse(); - assertThat(definition.isEnum()).isFalse(); - assertThat(definition.isNotNull()).isFalse(); - - } - - @Test - void isBinaryUsesCollationId() { - ColumnDefinition definition = ColumnDefinition.of(-1, CharCollation.BINARY_ID); - - assertThat(definition.isBinary()).isTrue(); - - definition = ColumnDefinition.of(-1, ~CharCollation.BINARY_ID); - assertThat(definition.isBinary()).isFalse(); - } -} diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java index 50c4d5122..b45d7f91c 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/ConnectionIntegrationTest.java @@ -16,6 +16,11 @@ 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.MySqlResult; +import io.asyncer.r2dbc.mysql.api.MySqlStatement; +import io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition; import io.r2dbc.spi.ColumnMetadata; import io.r2dbc.spi.R2dbcPermissionDeniedException; import io.r2dbc.spi.TransactionDefinition; @@ -23,6 +28,7 @@ import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.reactivestreams.Publisher; import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; import org.testcontainers.shaded.com.fasterxml.jackson.databind.node.ArrayNode; @@ -43,6 +49,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Objects; +import java.util.function.Function; import static io.r2dbc.spi.IsolationLevel.READ_COMMITTED; import static io.r2dbc.spi.IsolationLevel.READ_UNCOMMITTED; @@ -51,7 +58,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link MySqlConnection}. + * Integration tests for {@link MySqlSimpleConnection}. */ class ConnectionIntegrationTest extends IntegrationTestSupport { @@ -61,7 +68,7 @@ class ConnectionIntegrationTest extends IntegrationTestSupport { @Test void isInTransaction() { - complete(connection -> Mono.fromRunnable(() -> assertThat(connection.isInTransaction()) + castedComplete(connection -> Mono.fromRunnable(() -> assertThat(connection.isInTransaction()) .isFalse()) .then(connection.beginTransaction()) .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) @@ -76,16 +83,12 @@ void isInTransaction() { @DisabledIf("envIsLessThanMySql56") @Test void startTransaction() { - TransactionDefinition readOnlyConsistent = MySqlTransactionDefinition.builder() - .withConsistentSnapshot(true) - .readOnly(true) - .build(); - TransactionDefinition readWriteConsistent = MySqlTransactionDefinition.builder() - .withConsistentSnapshot(true) - .readOnly(false) - .build(); - - complete(connection -> Mono.fromRunnable(() -> assertThat(connection.isInTransaction()) + TransactionDefinition readOnlyConsistent = MySqlTransactionDefinition.mutability(false) + .consistent(); + TransactionDefinition readWriteConsistent = MySqlTransactionDefinition.mutability(true) + .consistent(); + + castedComplete(connection -> Mono.fromRunnable(() -> assertThat(connection.isInTransaction()) .isFalse()) .then(connection.beginTransaction(readOnlyConsistent)) .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) @@ -100,56 +103,60 @@ void startTransaction() { @Test void autoRollbackPreRelease() { // Mock pool allocate/release. - complete(conn -> conn.postAllocate() - .thenMany(conn.createStatement("CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY)") - .execute()) - .flatMap(MySqlResult::getRowsUpdated) - .then(conn.beginTransaction()) - .thenMany(conn.createStatement("INSERT INTO test VALUES (1)") - .execute()) - .flatMap(MySqlResult::getRowsUpdated) - .single() - .doOnNext(it -> assertThat(it).isEqualTo(1)) - .doOnSuccess(ignored -> assertThat(conn.isInTransaction()).isTrue()) - .then(conn.preRelease()) - .doOnSuccess(ignored -> assertThat(conn.isInTransaction()).isFalse()) - .then(conn.postAllocate()) - .thenMany(conn.createStatement("SELECT * FROM test") - .execute()) - .flatMap(it -> it.map((row, metadata) -> row.get(0, Integer.class))) - .count() - .doOnNext(it -> assertThat(it).isZero())); + complete(connection -> Mono.just(connection) + .cast(MySqlSimpleConnection.class) + .flatMap(conn -> connection.postAllocate() + .thenMany(conn.createStatement("CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY)") + .execute()) + .flatMap(MySqlResult::getRowsUpdated) + .then(conn.beginTransaction()) + .thenMany(conn.createStatement("INSERT INTO test VALUES (1)") + .execute()) + .flatMap(MySqlResult::getRowsUpdated) + .single() + .doOnNext(it -> assertThat(it).isEqualTo(1)) + .doOnSuccess(ignored -> assertThat(conn.isInTransaction()).isTrue()) + .then(conn.preRelease()) + .doOnSuccess(ignored -> assertThat(conn.isInTransaction()).isFalse()) + .then(conn.postAllocate()) + .thenMany(conn.createStatement("SELECT * FROM test") + .execute()) + .flatMap(it -> it.map((row, metadata) -> row.get(0, Integer.class))) + .count() + .doOnNext(it -> assertThat(it).isZero()))); } @Test void shouldNotRollbackCommittedPreRelease() { // Mock pool allocate/release. - complete(conn -> conn.postAllocate() - .thenMany(conn.createStatement("CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY)") - .execute()) - .flatMap(MySqlResult::getRowsUpdated) - .then(conn.beginTransaction()) - .thenMany(conn.createStatement("INSERT INTO test VALUES (1)") - .execute()) - .flatMap(MySqlResult::getRowsUpdated) - .single() - .doOnNext(it -> assertThat(it).isEqualTo(1)) - .then(conn.commitTransaction()) - .then(conn.preRelease()) - .doOnSuccess(ignored -> assertThat(conn.isInTransaction()).isFalse()) - .then(conn.postAllocate()) - .thenMany(conn.createStatement("SELECT * FROM test") - .execute()) - .flatMap(it -> it.map((row, metadata) -> row.get(0, Integer.class))) - .collectList() - .doOnNext(it -> assertThat(it).isEqualTo(Collections.singletonList(1)))); + complete(connection -> Mono.just(connection) + .cast(MySqlSimpleConnection.class) + .flatMap(conn -> conn.postAllocate() + .thenMany(conn.createStatement("CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY)") + .execute()) + .flatMap(MySqlResult::getRowsUpdated) + .then(conn.beginTransaction()) + .thenMany(conn.createStatement("INSERT INTO test VALUES (1)") + .execute()) + .flatMap(MySqlResult::getRowsUpdated) + .single() + .doOnNext(it -> assertThat(it).isEqualTo(1)) + .then(conn.commitTransaction()) + .then(conn.preRelease()) + .doOnSuccess(ignored -> assertThat(conn.isInTransaction()).isFalse()) + .then(conn.postAllocate()) + .thenMany(conn.createStatement("SELECT * FROM test") + .execute()) + .flatMap(it -> it.map((row, metadata) -> row.get(0, Integer.class))) + .collectList() + .doOnNext(it -> assertThat(it).isEqualTo(Collections.singletonList(1))))); } @Test void transactionDefinitionLockWaitTimeout() { - complete(connection -> connection.beginTransaction(MySqlTransactionDefinition.builder() - .lockWaitTimeout(Duration.ofSeconds(345)) - .build()) + castedComplete(connection -> connection + .beginTransaction(MySqlTransactionDefinition.empty() + .lockWaitTimeout(Duration.ofSeconds(345))) .doOnSuccess(ignored -> { assertThat(connection.isInTransaction()).isTrue(); assertThat(connection.getTransactionIsolationLevel()).isEqualTo(REPEATABLE_READ); @@ -165,9 +172,8 @@ void transactionDefinitionLockWaitTimeout() { @Test void transactionDefinitionIsolationLevel() { - complete(connection -> connection.beginTransaction(MySqlTransactionDefinition.builder() - .isolationLevel(READ_COMMITTED) - .build()) + castedComplete(connection -> connection + .beginTransaction(MySqlTransactionDefinition.from(READ_COMMITTED)) .doOnSuccess(ignored -> { assertThat(connection.isInTransaction()).isTrue(); assertThat(connection.getTransactionIsolationLevel()).isEqualTo(READ_COMMITTED); @@ -183,7 +189,7 @@ void transactionDefinitionIsolationLevel() { @Test void setTransactionLevelNotInTransaction() { - complete(connection -> + castedComplete(connection -> // check initial session isolation level Mono.fromSupplier(connection::getTransactionIsolationLevel) .doOnSuccess(it -> assertThat(it).isEqualTo(REPEATABLE_READ)) @@ -206,7 +212,7 @@ void setTransactionLevelNotInTransaction() { @Test void setTransactionLevelInTransaction() { - complete(connection -> + castedComplete(connection -> // check initial session transaction isolation level Mono.fromSupplier(connection::getTransactionIsolationLevel) .doOnSuccess(it -> assertThat(it).isEqualTo(REPEATABLE_READ)) @@ -229,11 +235,10 @@ void setTransactionLevelInTransaction() { @Test void transactionDefinition() { // The WITH CONSISTENT SNAPSHOT phrase can only be used with the REPEATABLE READ isolation level. - complete(connection -> connection.beginTransaction(MySqlTransactionDefinition.builder() + castedComplete(connection -> connection + .beginTransaction(MySqlTransactionDefinition.from(REPEATABLE_READ) .lockWaitTimeout(Duration.ofSeconds(112)) - .isolationLevel(REPEATABLE_READ) - .withConsistentSnapshot(true) - .build()) + .consistent()) .doOnSuccess(ignored -> { assertThat(connection.isInTransaction()).isTrue(); assertThat(connection.getTransactionIsolationLevel()).isEqualTo(REPEATABLE_READ); @@ -259,29 +264,29 @@ void setAutoCommit() { @Test void autoCommitAutomaticallyTurnedOffInTransaction() { complete(connection -> Mono.fromRunnable(() -> assertThat(connection.isAutoCommit()).isTrue()) - .then(connection.beginTransaction()) - .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) - .then(connection.commitTransaction()) - .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isTrue())); + .then(connection.beginTransaction()) + .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) + .then(connection.commitTransaction()) + .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isTrue())); } @Test void autoCommitStatusIsRestoredAfterTransaction() { complete(connection -> Mono.fromRunnable(() -> assertThat(connection.isAutoCommit()).isTrue()) - .then(connection.setAutoCommit(false)) - .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) - .then(connection.beginTransaction()) - .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) - .then(connection.commitTransaction()) - .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) - .then(connection.setAutoCommit(true)) - .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isTrue())); + .then(connection.setAutoCommit(false)) + .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) + .then(connection.beginTransaction()) + .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) + .then(connection.commitTransaction()) + .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isFalse()) + .then(connection.setAutoCommit(true)) + .doOnSuccess(ignored -> assertThat(connection.isAutoCommit()).isTrue())); } @ParameterizedTest @ValueSource(strings = { "test", "save`point" }) void createSavepointAndRollbackToSavepoint(String savepoint) { - complete(connection -> Mono.from(connection.createStatement( + castedComplete(connection -> Mono.from(connection.createStatement( "CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY, name VARCHAR(50))").execute()) .flatMap(IntegrationTestSupport::extractRowsUpdated) .then(connection.beginTransaction()) @@ -322,36 +327,36 @@ void createSavepointAndRollbackToSavepoint(String savepoint) { @ParameterizedTest @ValueSource(strings = { "test", "save`point" }) void createSavepointAndRollbackEntireTransaction(String savepoint) { - complete(connection -> Mono.from(connection.createStatement( - "CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY, name VARCHAR(50))").execute()) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .then(connection.beginTransaction()) - .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) - .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (1, 'test1')") - .execute())) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (2, 'test2')") - .execute())) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .then(Mono.from(connection.createStatement("SELECT COUNT(*) FROM test").execute())) - .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class)))) - .doOnSuccess(count -> assertThat(count).isEqualTo(2)) - .then(connection.createSavepoint(savepoint)) - .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) - .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (3, 'test3')") - .execute())) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (4, 'test4')") - .execute())) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .then(Mono.from(connection.createStatement("SELECT COUNT(*) FROM test").execute())) - .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class)))) - .doOnSuccess(count -> assertThat(count).isEqualTo(4)) - .then(connection.rollbackTransaction()) - .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isFalse()) - .then(Mono.from(connection.createStatement("SELECT COUNT(*) FROM test").execute())) - .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class)))) - .doOnSuccess(count -> assertThat(count).isEqualTo(0)) + castedComplete(connection -> Mono.from(connection.createStatement( + "CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY, name VARCHAR(50))").execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .then(connection.beginTransaction()) + .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) + .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (1, 'test1')") + .execute())) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (2, 'test2')") + .execute())) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .then(Mono.from(connection.createStatement("SELECT COUNT(*) FROM test").execute())) + .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class)))) + .doOnSuccess(count -> assertThat(count).isEqualTo(2)) + .then(connection.createSavepoint(savepoint)) + .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) + .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (3, 'test3')") + .execute())) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .then(Mono.from(connection.createStatement("INSERT INTO test VALUES (4, 'test4')") + .execute())) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .then(Mono.from(connection.createStatement("SELECT COUNT(*) FROM test").execute())) + .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class)))) + .doOnSuccess(count -> assertThat(count).isEqualTo(4)) + .then(connection.rollbackTransaction()) + .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isFalse()) + .then(Mono.from(connection.createStatement("SELECT COUNT(*) FROM test").execute())) + .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class)))) + .doOnSuccess(count -> assertThat(count).isEqualTo(0)) ); } @@ -376,72 +381,72 @@ void setTransactionIsolationLevel() { @Test void errorPropagteRequestQueue() { illegalArgument(connection -> Flux.merge( - connection.createStatement("SELECT 'Result 1', SLEEP(1)").execute(), - connection.createStatement("SELECT 'Result 2'").execute(), - connection.createStatement("SELECT 'Result 3'").execute() - ).flatMap(result -> result.map((row, meta) -> row.get(0, Integer.class))) + connection.createStatement("SELECT 'Result 1', SLEEP(1)").execute(), + connection.createStatement("SELECT 'Result 2'").execute(), + connection.createStatement("SELECT 'Result 3'").execute() + ).flatMap(result -> result.map((row, meta) -> row.get(0, Integer.class))) ); } @Test void commitTransactionShouldRespectQueuedMessages() { final String tdl = "CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY, name VARCHAR(50))"; - complete(connection -> - Mono.from(connection.createStatement(tdl).execute()) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .thenMany(Flux.merge( - connection.beginTransaction(), - connection.createStatement("INSERT INTO test VALUES (1, 'test1')") - .execute(), - connection.commitTransaction() - )) - .doOnComplete(() -> assertThat(connection.isInTransaction()).isFalse()) - .thenMany(connection.createStatement("SELECT COUNT(*) FROM test").execute()) - .flatMap(result -> - Mono.from(result.map((row, metadata) -> row.get(0, Long.class))) - ) - .doOnNext(text -> assertThat(text).isEqualTo(1L)) + castedComplete(connection -> + Mono.from(connection.createStatement(tdl).execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(Flux.merge( + connection.beginTransaction(), + connection.createStatement("INSERT INTO test VALUES (1, 'test1')") + .execute(), + connection.commitTransaction() + )) + .doOnComplete(() -> assertThat(connection.isInTransaction()).isFalse()) + .thenMany(connection.createStatement("SELECT COUNT(*) FROM test").execute()) + .flatMap(result -> + Mono.from(result.map((row, metadata) -> row.get(0, Long.class))) + ) + .doOnNext(text -> assertThat(text).isEqualTo(1L)) ); } @Test void rollbackTransactionShouldRespectQueuedMessages() { final String tdl = "CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY, name VARCHAR(50))"; - complete(connection -> - Mono.from(connection.createStatement(tdl).execute()) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .thenMany(Flux.merge( - connection.beginTransaction(), - connection.createStatement("INSERT INTO test VALUES (1, 'test1')") - .execute(), - connection.rollbackTransaction() - )) - .doOnComplete(() -> assertThat(connection.isInTransaction()).isFalse()) - .thenMany(connection.createStatement("SELECT COUNT(*) FROM test").execute()) - .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class))) - .doOnNext(count -> assertThat(count).isEqualTo(0L))) + castedComplete(connection -> + Mono.from(connection.createStatement(tdl).execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .thenMany(Flux.merge( + connection.beginTransaction(), + connection.createStatement("INSERT INTO test VALUES (1, 'test1')") + .execute(), + connection.rollbackTransaction() + )) + .doOnComplete(() -> assertThat(connection.isInTransaction()).isFalse()) + .thenMany(connection.createStatement("SELECT COUNT(*) FROM test").execute()) + .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class))) + .doOnNext(count -> assertThat(count).isEqualTo(0L))) ); } @Test void beginTransactionShouldRespectQueuedMessages() { final String tdl = "CREATE TEMPORARY TABLE test (id INT NOT NULL PRIMARY KEY, name VARCHAR(50))"; - complete(connection -> - Mono.from(connection.createStatement(tdl).execute()) - .flatMap(IntegrationTestSupport::extractRowsUpdated) - .then(Mono.from(connection.beginTransaction())) - .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) - .thenMany(Flux.merge( - connection.createStatement("INSERT INTO test VALUES (1, 'test1')").execute(), - connection.commitTransaction(), - connection.beginTransaction() - )) - .doOnComplete(() -> assertThat(connection.isInTransaction()).isTrue()) - .then(Mono.from(connection.rollbackTransaction())) - .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isFalse()) - .thenMany(connection.createStatement("SELECT COUNT(*) FROM test").execute()) - .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class))) - .doOnNext(count -> assertThat(count).isEqualTo(1L))) + castedComplete(connection -> + Mono.from(connection.createStatement(tdl).execute()) + .flatMap(IntegrationTestSupport::extractRowsUpdated) + .then(Mono.from(connection.beginTransaction())) + .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isTrue()) + .thenMany(Flux.merge( + connection.createStatement("INSERT INTO test VALUES (1, 'test1')").execute(), + connection.commitTransaction(), + connection.beginTransaction() + )) + .doOnComplete(() -> assertThat(connection.isInTransaction()).isTrue()) + .then(Mono.from(connection.rollbackTransaction())) + .doOnSuccess(ignored -> assertThat(connection.isInTransaction()).isFalse()) + .thenMany(connection.createStatement("SELECT COUNT(*) FROM test").execute()) + .flatMap(result -> Mono.from(result.map((row, metadata) -> row.get(0, Long.class))) + .doOnNext(count -> assertThat(count).isEqualTo(1L))) ); } @@ -593,6 +598,10 @@ void batchCrud() { }); } + private void castedComplete(Function> runner) { + complete(conn -> runner.apply((MySqlSimpleConnection) conn)); + } + private static String formattedSelect(String condition) { if (condition.isEmpty()) { return "SELECT id,value FROM test ORDER BY id"; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/InitDbIntegrationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/InitDbIntegrationTest.java index 66fb46e5a..efdeac68d 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/InitDbIntegrationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/InitDbIntegrationTest.java @@ -1,5 +1,6 @@ package io.asyncer.r2dbc.mysql; +import io.asyncer.r2dbc.mysql.api.MySqlResult; import org.junit.jupiter.api.Test; import java.util.concurrent.ThreadLocalRandom; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/IntegrationTestSupport.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/IntegrationTestSupport.java index fb83de493..5e5d046c0 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/IntegrationTestSupport.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/IntegrationTestSupport.java @@ -16,6 +16,7 @@ package io.asyncer.r2dbc.mysql; +import io.asyncer.r2dbc.mysql.api.MySqlConnection; import io.r2dbc.spi.R2dbcBadGrammarException; import io.r2dbc.spi.R2dbcTimeoutException; import io.r2dbc.spi.Result; @@ -61,7 +62,7 @@ void illegalArgument(Function> runner) { process(runner).expectError(IllegalArgumentException.class).verify(Duration.ofSeconds(3)); } - Mono create() { + Mono create() { return connectionFactory.create(); } diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/JacksonIntegrationTestSupport.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/JacksonIntegrationTestSupport.java index 79a851693..10e138607 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/JacksonIntegrationTestSupport.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/JacksonIntegrationTestSupport.java @@ -18,12 +18,15 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import io.asyncer.r2dbc.mysql.api.MySqlConnection; +import io.asyncer.r2dbc.mysql.api.MySqlResult; +import io.asyncer.r2dbc.mysql.api.MySqlStatement; import io.asyncer.r2dbc.mysql.json.JacksonCodecRegistrar; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; -import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -81,7 +84,7 @@ void json() { .verifyComplete(); } - private static Publisher insert(MySqlConnection connection) { + private static Flux insert(MySqlConnection connection) { MySqlStatement statement = connection.createStatement("INSERT INTO test VALUES (DEFAULT, ?)"); for (int i = 0; i < BARS.length; ++i) { diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MariaDbIntegrationTestSupport.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MariaDbIntegrationTestSupport.java index dcd9fd482..00d192de3 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MariaDbIntegrationTestSupport.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MariaDbIntegrationTestSupport.java @@ -16,6 +16,7 @@ package io.asyncer.r2dbc.mysql; +import io.asyncer.r2dbc.mysql.api.MySqlConnection; import io.r2dbc.spi.Readable; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnectionTest.java similarity index 91% rename from r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionTest.java rename to r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnectionTest.java index 2cec69c8f..9fa2395b8 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlSimpleConnectionTest.java @@ -35,9 +35,9 @@ import static org.mockito.Mockito.when; /** - * Unit tests for {@link MySqlConnection}. + * Unit tests for {@link MySqlSimpleConnection}. */ -class MySqlConnectionTest { +class MySqlSimpleConnectionTest { private final Client client = mock(Client.class); @@ -47,20 +47,20 @@ class MySqlConnectionTest { private final String product = "MockConnection"; - private final MySqlConnection noPrepare = new MySqlConnection(client, ConnectionContextTest.mock(), + private final MySqlSimpleConnection noPrepare = new MySqlSimpleConnection(client, ConnectionContextTest.mock(), codecs, level, 50, Caches.createQueryCache(0), Caches.createPrepareCache(0), product, null); @Test void createStatement() { String condition = "SELECT * FROM test"; - MySqlConnection allPrepare = new MySqlConnection(client, ConnectionContextTest.mock(), + MySqlSimpleConnection allPrepare = new MySqlSimpleConnection(client, ConnectionContextTest.mock(), codecs, level, 50, Caches.createQueryCache(0), Caches.createPrepareCache(0), product, sql -> true); - MySqlConnection halfPrepare = new MySqlConnection(client, ConnectionContextTest.mock(), + MySqlSimpleConnection halfPrepare = new MySqlSimpleConnection(client, ConnectionContextTest.mock(), codecs, level, 50, Caches.createQueryCache(0), Caches.createPrepareCache(0), product, sql -> false); - MySqlConnection conditionPrepare = new MySqlConnection(client, ConnectionContextTest.mock(), + MySqlSimpleConnection conditionPrepare = new MySqlSimpleConnection(client, ConnectionContextTest.mock(), codecs, level, 50, Caches.createQueryCache(0), Caches.createPrepareCache(0), product, sql -> sql.equals(condition)); diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinitionTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinitionTest.java deleted file mode 100644 index 7ff20cb9d..000000000 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTransactionDefinitionTest.java +++ /dev/null @@ -1,80 +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.IsolationLevel; -import io.r2dbc.spi.TransactionDefinition; -import org.junit.jupiter.api.Test; - -import java.time.Duration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link MySqlTransactionDefinition}. - */ -class MySqlTransactionDefinitionTest { - - @Test - void builder() { - Duration lockWaitTimeout = Duration.ofSeconds(118); - Long sessionId = 123456789L; - MySqlTransactionDefinition definition = MySqlTransactionDefinition.builder() - .isolationLevel(IsolationLevel.READ_COMMITTED) - .lockWaitTimeout(lockWaitTimeout) - .withConsistentSnapshot(true) - .consistentSnapshotEngine(ConsistentSnapshotEngine.ROCKSDB) - .consistentSnapshotFromSession(sessionId) - .build(); - - assertThat(definition.getAttribute(TransactionDefinition.ISOLATION_LEVEL)) - .isSameAs(IsolationLevel.READ_COMMITTED); - assertThat(definition.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) - .isSameAs(lockWaitTimeout); - assertThat(definition.getAttribute(MySqlTransactionDefinition.WITH_CONSISTENT_SNAPSHOT)) - .isTrue(); - assertThat(definition.getAttribute(MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_ENGINE)) - .isSameAs(ConsistentSnapshotEngine.ROCKSDB); - assertThat(definition.getAttribute(MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_FROM_SESSION)) - .isSameAs(sessionId); - } - - @Test - void mutate() { - Duration lockWaitTimeout = Duration.ofSeconds(118); - MySqlTransactionDefinition def1 = MySqlTransactionDefinition.builder() - .isolationLevel(IsolationLevel.SERIALIZABLE) - .lockWaitTimeout(lockWaitTimeout) - .readOnly(true) - .build(); - MySqlTransactionDefinition def2 = def1.mutate() - .isolationLevel(IsolationLevel.READ_COMMITTED) - .build(); - - assertThat(def1).isNotEqualTo(def2); - assertThat(def1.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) - .isSameAs(def2.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) - .isSameAs(lockWaitTimeout); - assertThat(def1.getAttribute(TransactionDefinition.ISOLATION_LEVEL)) - .isSameAs(IsolationLevel.SERIALIZABLE); - assertThat(def2.getAttribute(TransactionDefinition.ISOLATION_LEVEL)) - .isSameAs(IsolationLevel.READ_COMMITTED); - assertThat(def1.getAttribute(TransactionDefinition.READ_ONLY)) - .isSameAs(def2.getAttribute(TransactionDefinition.READ_ONLY)) - .isEqualTo(true); - } -} diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadataTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadataTest.java new file mode 100644 index 000000000..044e74a55 --- /dev/null +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlTypeMetadataTest.java @@ -0,0 +1,60 @@ +/* + * 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; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link MySqlTypeMetadata}. + */ +class MySqlTypeMetadataTest { + + @Test + void allSet() { + MySqlTypeMetadata metadata = new MySqlTypeMetadata(0, -1, 0); + + assertThat(metadata.isBinary()).isTrue(); + assertThat(metadata.isSet()).isTrue(); + assertThat(metadata.isUnsigned()).isTrue(); + assertThat(metadata.isEnum()).isTrue(); + assertThat(metadata.isNotNull()).isTrue(); + } + + @Test + void noSet() { + MySqlTypeMetadata metadata = new MySqlTypeMetadata(0, 0, 0); + + assertThat(metadata.isBinary()).isFalse(); + assertThat(metadata.isSet()).isFalse(); + assertThat(metadata.isUnsigned()).isFalse(); + assertThat(metadata.isEnum()).isFalse(); + assertThat(metadata.isNotNull()).isFalse(); + } + + @Test + void isBinaryUsesCollationId() { + MySqlTypeMetadata metadata = new MySqlTypeMetadata(0, -1, CharCollation.BINARY_ID); + + assertThat(metadata.isBinary()).isTrue(); + + metadata = new MySqlTypeMetadata(0, -1, 33); + assertThat(metadata.isBinary()).isFalse(); + } +} diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/QueryIntegrationTestSupport.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/QueryIntegrationTestSupport.java index 25041c282..fda842503 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/QueryIntegrationTestSupport.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/QueryIntegrationTestSupport.java @@ -17,8 +17,10 @@ package io.asyncer.r2dbc.mysql; import com.fasterxml.jackson.core.type.TypeReference; -import io.r2dbc.spi.Connection; -import io.r2dbc.spi.Result; +import io.asyncer.r2dbc.mysql.api.MySqlConnection; +import io.asyncer.r2dbc.mysql.api.MySqlResult; +import io.asyncer.r2dbc.mysql.api.MySqlRow; +import io.asyncer.r2dbc.mysql.api.MySqlStatement; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; @@ -634,12 +636,12 @@ private static JsonNode parseJson(String json) { } } - private static Flux extractFirstInteger(Result result) { + private static Flux extractFirstInteger(MySqlResult result) { return Flux.from(result.map((row, metadata) -> row.get(0, Integer.class))); } @SuppressWarnings("unchecked") - private static Flux> extractOptionalField(Result result, Type type) { + private static Flux> extractOptionalField(MySqlResult result, Type type) { if (type instanceof Class) { return Flux.from(result.map((row, metadata) -> Optional.ofNullable(row.get(0, (Class) type)))); } @@ -647,7 +649,7 @@ private static Flux> extractOptionalField(Result result, Type ty Optional.ofNullable(((MySqlRow) row).get(0, (ParameterizedType) type)))); } - private static Mono testTimeDuration(Connection connection, Duration origin, LocalTime time) { + private static Mono testTimeDuration(MySqlConnection connection, Duration origin, LocalTime time) { return Mono.from(connection.createStatement("INSERT INTO test VALUES(DEFAULT,?)") .bind(0, origin) .returnGeneratedValues("id") diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/SslTunnelIntegrationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/SslTunnelIntegrationTest.java index 853b11809..7313be994 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/SslTunnelIntegrationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/SslTunnelIntegrationTest.java @@ -16,6 +16,7 @@ package io.asyncer.r2dbc.mysql; +import io.asyncer.r2dbc.mysql.api.MySqlConnection; import io.asyncer.r2dbc.mysql.constant.SslMode; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StartTransactionStateTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StartTransactionStateTest.java index 14a878d91..132195cbb 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StartTransactionStateTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StartTransactionStateTest.java @@ -16,6 +16,7 @@ package io.asyncer.r2dbc.mysql; +import io.asyncer.r2dbc.mysql.api.MySqlTransactionDefinition; import io.r2dbc.spi.IsolationLevel; import io.r2dbc.spi.TransactionDefinition; import org.junit.jupiter.params.ParameterizedTest; @@ -40,32 +41,26 @@ void buildStartTransaction(TransactionDefinition definition, String excepted) { static Stream buildStartTransaction() { return Stream.of( Arguments.of(MySqlTransactionDefinition.empty(), "BEGIN"), - Arguments.of(MySqlTransactionDefinition.builder() - .isolationLevel(IsolationLevel.READ_UNCOMMITTED) - .build(), "BEGIN"), - Arguments.of(MySqlTransactionDefinition.builder() - .readOnly(true) - .build(), "START TRANSACTION READ ONLY"), - Arguments.of(MySqlTransactionDefinition.builder() - .readOnly(false) - .build(), "START TRANSACTION READ WRITE"), - Arguments.of(MySqlTransactionDefinition.builder() - .withConsistentSnapshot(true) - .build(), "START TRANSACTION WITH CONSISTENT SNAPSHOT"), - Arguments.of(MySqlTransactionDefinition.builder() - .withConsistentSnapshot(true) - .readOnly(true) - .build(), "START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY"), - Arguments.of(MySqlTransactionDefinition.builder() - .withConsistentSnapshot(true) - .readOnly(false) - .build(), "START TRANSACTION WITH CONSISTENT SNAPSHOT, READ WRITE"), - Arguments.of(MySqlTransactionDefinition.builder() - .withConsistentSnapshot(true) - .consistentSnapshotEngine(ConsistentSnapshotEngine.ROCKSDB) - .consistentSnapshotFromSession(3L) - .readOnly(true) - .build(), "START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT FROM SESSION 3, READ ONLY") - ); + Arguments.of(MySqlTransactionDefinition.from(IsolationLevel.READ_UNCOMMITTED), "BEGIN"), + Arguments.of(MySqlTransactionDefinition.mutability(false), "START TRANSACTION READ ONLY"), + Arguments.of(MySqlTransactionDefinition.mutability(true), "START TRANSACTION READ WRITE"), + Arguments.of( + MySqlTransactionDefinition.empty().consistent(), + "START TRANSACTION WITH CONSISTENT SNAPSHOT" + ), + Arguments.of( + MySqlTransactionDefinition.mutability(false).consistent(), + "START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY" + ), + Arguments.of( + MySqlTransactionDefinition.mutability(true).consistent(), + "START TRANSACTION WITH CONSISTENT SNAPSHOT, READ WRITE" + ), + Arguments.of( + MySqlTransactionDefinition.mutability(false) + .consistent("ROCKSDB", 3L), + "START TRANSACTION WITH CONSISTENT ROCKSDB SNAPSHOT FROM SESSION 3, READ ONLY" + ) + ); } } diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StatementTestSupport.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StatementTestSupport.java index 6732b6ec4..0cab3c695 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StatementTestSupport.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/StatementTestSupport.java @@ -16,6 +16,7 @@ package io.asyncer.r2dbc.mysql; +import io.asyncer.r2dbc.mysql.api.MySqlStatement; import org.junit.jupiter.api.Test; import java.util.NoSuchElementException; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TimeZoneIntegrationTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TimeZoneIntegrationTest.java index 0d302494c..99da15e3c 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TimeZoneIntegrationTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/TimeZoneIntegrationTest.java @@ -1,6 +1,7 @@ package io.asyncer.r2dbc.mysql; import com.zaxxer.hikari.HikariDataSource; +import io.asyncer.r2dbc.mysql.api.MySqlResult; import org.assertj.core.data.TemporalUnitOffset; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinitionTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinitionTest.java new file mode 100644 index 000000000..46fa69ef0 --- /dev/null +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/api/MySqlTransactionDefinitionTest.java @@ -0,0 +1,172 @@ +/* + * 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.asyncer.r2dbc.mysql.ConsistentSnapshotEngine; +import io.r2dbc.spi.IsolationLevel; +import io.r2dbc.spi.TransactionDefinition; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.time.Duration; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link MySqlTransactionDefinition}. + */ +class MySqlTransactionDefinitionTest { + + @Test + void getAttribute() { + Duration lockWaitTimeout = Duration.ofSeconds(118); + long sessionId = 123456789L; + MySqlTransactionDefinition definition = MySqlTransactionDefinition.from(IsolationLevel.READ_COMMITTED) + .lockWaitTimeout(lockWaitTimeout) + .consistent("ROCKSDB", sessionId); + + assertThat(definition.getAttribute(TransactionDefinition.ISOLATION_LEVEL)) + .isSameAs(IsolationLevel.READ_COMMITTED); + assertThat(definition.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) + .isSameAs(lockWaitTimeout); + assertThat(definition.getAttribute(MySqlTransactionDefinition.WITH_CONSISTENT_SNAPSHOT)) + .isTrue(); + assertThat(definition.getAttribute(MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_ENGINE)) + .isEqualTo("ROCKSDB"); + assertThat(definition.getAttribute(MySqlTransactionDefinition.CONSISTENT_SNAPSHOT_FROM_SESSION)) + .isEqualTo(sessionId); + } + + @Test + void isolationLevel() { + Duration lockWaitTimeout = Duration.ofSeconds(118); + MySqlTransactionDefinition def1 = MySqlTransactionDefinition.mutability(false) + .isolationLevel(IsolationLevel.SERIALIZABLE) + .lockWaitTimeout(lockWaitTimeout); + MySqlTransactionDefinition def2 = def1.isolationLevel(IsolationLevel.READ_COMMITTED); + + assertThat(def1).isNotEqualTo(def2); + assertThat(def1.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) + .isSameAs(def2.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) + .isSameAs(lockWaitTimeout); + assertThat(def1.getAttribute(TransactionDefinition.ISOLATION_LEVEL)) + .isSameAs(IsolationLevel.SERIALIZABLE); + assertThat(def2.getAttribute(TransactionDefinition.ISOLATION_LEVEL)) + .isSameAs(IsolationLevel.READ_COMMITTED); + assertThat(def1.getAttribute(TransactionDefinition.READ_ONLY)) + .isSameAs(def2.getAttribute(TransactionDefinition.READ_ONLY)) + .isEqualTo(true); + } + + @ParameterizedTest + @MethodSource + void withoutIsolationLevel(MySqlTransactionDefinition definition, IsolationLevel level) { + assertThat(definition.getAttribute(TransactionDefinition.ISOLATION_LEVEL)) + .isSameAs(level); + assertThat(definition.withoutIsolationLevel().getAttribute(TransactionDefinition.ISOLATION_LEVEL)) + .isNull(); + } + + @ParameterizedTest + @MethodSource + void withoutLockWaitTimeout(MySqlTransactionDefinition definition, @Nullable Duration lockWaitTimeout) { + assertThat(definition.getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) + .isEqualTo(lockWaitTimeout); + assertThat(definition.withoutLockWaitTimeout().getAttribute(TransactionDefinition.LOCK_WAIT_TIMEOUT)) + .isNull(); + } + + @ParameterizedTest + @MethodSource + void withoutMutability(MySqlTransactionDefinition definition, @Nullable Boolean readOnly) { + assertThat(definition.getAttribute(TransactionDefinition.READ_ONLY)) + .isEqualTo(readOnly); + assertThat(definition.withoutMutability().getAttribute(TransactionDefinition.READ_ONLY)) + .isNull(); + } + + @ParameterizedTest + @MethodSource + void withoutConsistent(MySqlTransactionDefinition definition) { + assertThat(definition.getAttribute(MySqlTransactionDefinition.WITH_CONSISTENT_SNAPSHOT)) + .isTrue(); + assertThat(definition) + .isNotEqualTo(MySqlTransactionDefinition.empty()) + .extracting(MySqlTransactionDefinition::withoutConsistent) + .isEqualTo(MySqlTransactionDefinition.empty()); + } + + static Stream withoutIsolationLevel() { + return Stream.of( + Arguments.of(MySqlTransactionDefinition.empty(), null), + Arguments.of( + MySqlTransactionDefinition.from(IsolationLevel.READ_COMMITTED), + IsolationLevel.READ_COMMITTED + ), + Arguments.of( + MySqlTransactionDefinition.from(IsolationLevel.SERIALIZABLE) + .lockWaitTimeout(Duration.ofSeconds(118)), + IsolationLevel.SERIALIZABLE + ) + ); + } + + static Stream withoutLockWaitTimeout() { + return Stream.of( + Arguments.of(MySqlTransactionDefinition.empty(), null), + Arguments.of( + MySqlTransactionDefinition.empty() + .lockWaitTimeout(Duration.ofSeconds(118)), + Duration.ofSeconds(118) + ), + Arguments.of( + MySqlTransactionDefinition.empty() + .lockWaitTimeout(Duration.ofSeconds(123)) + .consistent("ROCKSDB", 123456789), + Duration.ofSeconds(123) + ) + ); + } + + static Stream withoutMutability() { + return Stream.of( + Arguments.of(MySqlTransactionDefinition.empty(), null), + Arguments.of(MySqlTransactionDefinition.mutability(true), false), + Arguments.of(MySqlTransactionDefinition.mutability(false), true), + Arguments.of(MySqlTransactionDefinition.mutability(true).consistent(), false), + Arguments.of(MySqlTransactionDefinition.mutability(false).consistent(), true), + Arguments.of( + MySqlTransactionDefinition.mutability(true) + .isolationLevel(IsolationLevel.SERIALIZABLE), + false + ) + ); + } + + static Stream withoutConsistent() { + return Stream.of( + MySqlTransactionDefinition.empty().consistent(), + MySqlTransactionDefinition.empty().consistent("ROCKSDB"), + MySqlTransactionDefinition.empty().consistent("ROCKSDB", 123456789), + MySqlTransactionDefinition.empty().consistentFromSession(123456789) + ); + } +} diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/CodecsTest.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/CodecsTest.java index 87d097a86..fefdb2415 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/CodecsTest.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/CodecsTest.java @@ -17,8 +17,7 @@ package io.asyncer.r2dbc.mysql.codec; import io.asyncer.r2dbc.mysql.ConnectionContextTest; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; -import io.asyncer.r2dbc.mysql.MySqlTypeMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.collation.CharCollation; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.message.FieldValue; @@ -73,21 +72,11 @@ public String getName() { return "mock"; } - @Override - public MySqlTypeMetadata getNativeTypeMetadata() { - return null; - } - @Override public CharCollation getCharCollation(CodecContext context) { return CharCollation.fromId(CharCollation.BINARY_ID, context); } - @Override - public long getNativePrecision() { - return 0; - } - @Override public Nullability getNullability() { return Nullability.NULLABLE; diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/Decoding.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/Decoding.java index 7faade837..bf17a6dc9 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/Decoding.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/codec/Decoding.java @@ -16,8 +16,7 @@ package io.asyncer.r2dbc.mysql.codec; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; -import io.asyncer.r2dbc.mysql.MySqlTypeMetadata; +import io.asyncer.r2dbc.mysql.api.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.collation.CharCollation; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.netty.buffer.ByteBuf; @@ -71,11 +70,6 @@ public String getName() { return "mock"; } - @Override - public MySqlTypeMetadata getNativeTypeMetadata() { - return null; - } - @Override public Nullability getNullability() { return Nullability.NON_NULL; @@ -86,9 +80,5 @@ public CharCollation getCharCollation(CodecContext context) { return context.getClientCollation(); } - @Override - public long getNativePrecision() { - return 0; - } } } diff --git a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/json/JacksonCodec.java b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/json/JacksonCodec.java index dbaeeb928..9954721d2 100644 --- a/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/json/JacksonCodec.java +++ b/r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/json/JacksonCodec.java @@ -17,11 +17,12 @@ package io.asyncer.r2dbc.mysql.json; import com.fasterxml.jackson.databind.ObjectMapper; -import io.asyncer.r2dbc.mysql.MySqlColumnMetadata; import io.asyncer.r2dbc.mysql.MySqlParameter; import io.asyncer.r2dbc.mysql.ParameterWriter; +import io.asyncer.r2dbc.mysql.api.MySqlReadableMetadata; import io.asyncer.r2dbc.mysql.codec.CodecContext; import io.asyncer.r2dbc.mysql.codec.ParametrizedCodec; +import io.asyncer.r2dbc.mysql.collation.CharCollation; import io.asyncer.r2dbc.mysql.constant.MySqlType; import io.asyncer.r2dbc.mysql.internal.util.VarIntUtils; import io.netty.buffer.ByteBuf; @@ -52,7 +53,7 @@ public JacksonCodec(Mode mode) { } @Override - public Object decode(ByteBuf value, MySqlColumnMetadata metadata, Class target, boolean binary, + public Object decode(ByteBuf value, MySqlReadableMetadata metadata, Class target, boolean binary, CodecContext context) { Charset charset = metadata.getCharCollation(context).getCharset(); @@ -64,7 +65,7 @@ public Object decode(ByteBuf value, MySqlColumnMetadata metadata, Class targe } @Override - public Object decode(ByteBuf value, MySqlColumnMetadata metadata, ParameterizedType target, boolean binary, + public Object decode(ByteBuf value, MySqlReadableMetadata metadata, ParameterizedType target, boolean binary, CodecContext context) { Charset charset = metadata.getCharCollation(context).getCharset(); @@ -81,12 +82,12 @@ public MySqlParameter encode(Object value, CodecContext context) { } @Override - public boolean canDecode(MySqlColumnMetadata metadata, Class target) { + public boolean canDecode(MySqlReadableMetadata metadata, Class target) { return doCanDecode(metadata); } @Override - public boolean canDecode(MySqlColumnMetadata metadata, ParameterizedType target) { + public boolean canDecode(MySqlReadableMetadata metadata, ParameterizedType target) { return doCanDecode(metadata); } @@ -95,7 +96,7 @@ public boolean canEncode(Object value) { return mode.isEncode(); } - private boolean doCanDecode(MySqlColumnMetadata metadata) { + private boolean doCanDecode(MySqlReadableMetadata metadata) { return mode.isDecode() && (metadata.getType() == MySqlType.JSON || metadata.getType() == MySqlType.TEXT); }