From b9637cedb6d0ed5fcd534dd5b7a32d1521b54947 Mon Sep 17 00:00:00 2001 From: Billy Yuan Date: Tue, 26 Jan 2021 22:52:26 +0800 Subject: [PATCH] initial work for custom datatype codec Signed-off-by: Billy Yuan --- .../main/java/examples/SqlClientExamples.java | 170 ++++++++++++++++++ .../vertx/mysqlclient/impl/MySQLRowImpl.java | 3 +- .../impl/MySQLSocketConnection.java | 7 + .../mysqlclient/impl/codec/CommandCodec.java | 2 +- .../codec/ExtendedBatchQueryCommandCodec.java | 1 + .../impl/codec/ExtendedQueryCommandCodec.java | 14 +- .../impl/codec/QueryCommandBaseCodec.java | 2 +- .../impl/codec/RowResultDecoder.java | 85 ++++++++- .../impl/protocol/ColumnDefinition.java | 10 +- .../typecodec/MySQLDataTypeCodecRegistry.java | 105 +++++++++++ .../typecodec/MySQLDataTypeDefaultCodecs.java | 123 +++++++++++++ .../mysqlclient/typecodec/MySQLType.java | 56 ++++++ .../io/vertx/sqlclient/SqlConnection.java | 4 +- .../io/vertx/sqlclient/codec/DataType.java | 26 +++ .../vertx/sqlclient/codec/DataTypeCodec.java | 27 +++ .../codec/DataTypeCodecRegistry.java | 24 +++ .../io/vertx/sqlclient/impl/Connection.java | 3 + .../sqlclient/impl/SqlConnectionImpl.java | 6 + 18 files changed, 649 insertions(+), 19 deletions(-) create mode 100644 vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeCodecRegistry.java create mode 100644 vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeDefaultCodecs.java create mode 100644 vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLType.java create mode 100644 vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataType.java create mode 100644 vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodec.java create mode 100644 vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodecRegistry.java diff --git a/vertx-mysql-client/src/main/java/examples/SqlClientExamples.java b/vertx-mysql-client/src/main/java/examples/SqlClientExamples.java index 3f1b88668..7c089dab4 100644 --- a/vertx-mysql-client/src/main/java/examples/SqlClientExamples.java +++ b/vertx-mysql-client/src/main/java/examples/SqlClientExamples.java @@ -16,11 +16,15 @@ */ package examples; +import io.netty.buffer.ByteBuf; import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.tracing.TracingPolicy; import io.vertx.docgen.Source; import io.vertx.mysqlclient.MySQLConnectOptions; +import io.vertx.mysqlclient.typecodec.MySQLDataTypeDefaultCodecs; +import io.vertx.mysqlclient.typecodec.MySQLType; import io.vertx.sqlclient.Cursor; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; @@ -33,7 +37,13 @@ import io.vertx.sqlclient.SqlConnection; import io.vertx.sqlclient.Transaction; import io.vertx.sqlclient.Tuple; +import io.vertx.sqlclient.codec.DataType; +import io.vertx.sqlclient.codec.DataTypeCodec; +import io.vertx.sqlclient.codec.DataTypeCodecRegistry; +import io.vertx.sqlclient.impl.codec.CommonCodec; +import java.nio.charset.Charset; +import java.sql.JDBCType; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -328,4 +338,164 @@ public void usingCursors03(SqlConnection connection) { public void tracing01(MySQLConnectOptions options) { options.setTracingPolicy(TracingPolicy.ALWAYS); } + + public void customDataTypeCodecExample01(SqlConnection connection) { + // usecase01-byte as boolean + DataTypeCodec tinyintCodec = new DataTypeCodec() { + + private final DataType tinyintDatatype = new DataType() { + @Override + public int identifier() { + return MySQLType.TINYINT.identifier(); + } + + @Override + public JDBCType jdbcType() { + return JDBCType.TINYINT; + } + + @Override + public Class encodingJavaClass() { + return Boolean.class; + } + + @Override + public Class decodingJavaClass() { + return Boolean.class; + } + }; + + @Override + public DataType dataType() { + return tinyintDatatype; + } + + @Override + public void encode(ByteBuf buffer, Boolean value) { + buffer.writeBoolean(value); + } + + @Override + public Boolean binaryDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + return buffer.readBoolean(); + } + + @Override + public Boolean textualDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + byte b = (byte) CommonCodec.decodeDecStringToLong(readerIndex, (int) length, buffer); + return b != 0; + } + }; + + DataTypeCodecRegistry dataTypeCodecRegistry = connection.dataTypeCodecRegistry(); + dataTypeCodecRegistry.unregister(MySQLDataTypeDefaultCodecs.TinyIntTypeCodec.INSTANCE); + dataTypeCodecRegistry.register(tinyintCodec); + } + + public void customDataTypeCodecExample02(SqlConnection connection) { + // usecase02-custom JSON https://github.com/eclipse-vertx/vertx-sql-client/issues/862 + DataTypeCodec jsonCustomTypeCodec = new DataTypeCodec() { + + private final DataType customJSONDatatype = new DataType() { + @Override + public int identifier() { + return MySQLType.JSON.identifier(); + } + + @Override + public JDBCType jdbcType() { + return JDBCType.OTHER; + } + + @Override + public Class encodingJavaClass() { + return CustomJSON.class; + } + + @Override + public Class decodingJavaClass() { + return CustomJSON.class; + } + }; + + @Override + public DataType dataType() { + return customJSONDatatype; + } + + @Override + public void encode(ByteBuf buffer, CustomJSON value) { + // write to bytebuf directly from the custom json + // ... + } + + @Override + public CustomJSON binaryDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + // read from bytebuf directly + // ... + } + + @Override + public CustomJSON textualDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + // read from bytebuf directly + // ... + } + }; + + DataTypeCodecRegistry dataTypeCodecRegistry = connection.dataTypeCodecRegistry(); + dataTypeCodecRegistry.unregister(MySQLDataTypeDefaultCodecs.JsonTypeCodec.INSTANCE); + dataTypeCodecRegistry.register(jsonCustomTypeCodec); + } + + public void customDataTypeCodecExample03(SqlConnection connection) { + // usecase03 - row values direct buffer + DataTypeCodec rowValueRawBufCodec = new DataTypeCodec() { + + private final DataType tinyintDatatype = new DataType() { + @Override + public int identifier() { + return MySQLType.TINYINT.identifier(); + } + + @Override + public JDBCType jdbcType() { + return JDBCType.TINYINT; + } + + @Override + public Class encodingJavaClass() { + return ByteBuf.class; + } + + @Override + public Class decodingJavaClass() { + return ByteBuf.class; + } + }; + + @Override + public DataType dataType() { + return tinyintDatatype; + } + + @Override + public void encode(ByteBuf buffer, ByteBuf value) { + buffer.writeBytes(value); + } + + @Override + public ByteBuf binaryDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + return buffer.readRetainedSlice((int) length); + } + + @Override + public ByteBuf textualDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + return buffer.readRetainedSlice((int) length); + } + }; + + DataTypeCodecRegistry dataTypeCodecRegistry = connection.dataTypeCodecRegistry(); + dataTypeCodecRegistry.unregister(MySQLDataTypeDefaultCodecs.TinyIntTypeCodec.INSTANCE); + dataTypeCodecRegistry.register(rowValueRawBufCodec); + } } diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLRowImpl.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLRowImpl.java index c1c431053..9653ba80e 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLRowImpl.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLRowImpl.java @@ -14,7 +14,6 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.mysqlclient.data.spatial.*; -import io.vertx.mysqlclient.impl.datatype.DataType; import io.vertx.mysqlclient.impl.protocol.ColumnDefinition; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.data.Numeric; @@ -246,7 +245,7 @@ private GeometryCollection getGeometryCollection(int pos) { public LocalTime getLocalTime(int pos) { ColumnDefinition columnDefinition = rowDesc.columnDefinitions()[pos]; Object val = getValue(pos); - if (columnDefinition.type() == DataType.TIME && val instanceof Duration) { + if (columnDefinition.type() == ColumnDefinition.ColumnType.MYSQL_TYPE_TIME && val instanceof Duration) { // map MySQL TIME data type to java.time.LocalTime Duration duration = (Duration) val; return LocalTime.ofNanoOfDay(duration.toNanos()); diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLSocketConnection.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLSocketConnection.java index 9ee5383b0..3de9d963e 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLSocketConnection.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/MySQLSocketConnection.java @@ -27,6 +27,8 @@ import io.vertx.mysqlclient.SslMode; import io.vertx.mysqlclient.impl.codec.MySQLCodec; import io.vertx.mysqlclient.impl.command.InitialHandshakeCommand; +import io.vertx.mysqlclient.typecodec.MySQLDataTypeCodecRegistry; +import io.vertx.sqlclient.codec.DataTypeCodecRegistry; import io.vertx.sqlclient.impl.Connection; import io.vertx.sqlclient.impl.QueryResultHandler; import io.vertx.sqlclient.impl.SocketConnectionBase; @@ -103,4 +105,9 @@ public void upgradeToSsl(Handler> completionHandler) { public DatabaseMetadata getDatabaseMetaData() { return metaData; } + + @Override + public MySQLDataTypeCodecRegistry getDataTypeCodecRegistry() { + return MySQLDataTypeCodecRegistry.INSTANCE; + } } diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/CommandCodec.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/CommandCodec.java index e4ec2b045..43abdc54a 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/CommandCodec.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/CommandCodec.java @@ -175,7 +175,7 @@ ColumnDefinition decodeColumnDefinitionPacketPayload(ByteBuf payload) { long lengthOfFixedLengthFields = BufferUtils.readLengthEncodedInteger(payload); int characterSet = payload.readUnsignedShortLE(); long columnLength = payload.readUnsignedIntLE(); - DataType type = DataType.valueOf(payload.readUnsignedByte()); + short type = payload.readUnsignedByte(); int flags = payload.readUnsignedShortLE(); byte decimals = payload.readByte(); return new ColumnDefinition(catalog, schema, table, orgTable, name, orgName, characterSet, columnLength, type, flags, decimals); diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java index d8df9cfb5..c12a950bc 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedBatchQueryCommandCodec.java @@ -106,6 +106,7 @@ private void sendBatchStatementExecuteCommand(MySQLPreparedStatement statement, for (int i = 0; i < numOfParams; i++) { Object value = params.getValue(i); if (value != null) { + // FIXME encoding using customized codec DataTypeCodec.encodeBinary(DataTypeCodec.inferDataTypeByEncodingValue(value), value, encoder.encodingCharset, packet); } else { nullBitmap[i / 8] |= (1 << (i & 7)); diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedQueryCommandCodec.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedQueryCommandCodec.java index e9f657b41..112ba8e26 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedQueryCommandCodec.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/ExtendedQueryCommandCodec.java @@ -20,6 +20,7 @@ import io.vertx.mysqlclient.impl.datatype.DataType; import io.vertx.mysqlclient.impl.datatype.DataTypeCodec; import io.vertx.mysqlclient.impl.protocol.CommandType; +import io.vertx.mysqlclient.typecodec.MySQLDataTypeCodecRegistry; import io.vertx.sqlclient.Tuple; import io.vertx.sqlclient.impl.command.CommandResponse; import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; @@ -42,7 +43,7 @@ void encode(MySQLEncoder encoder) { super.encode(encoder); if (statement.isCursorOpen) { - decoder = new RowResultDecoder<>(cmd.collector(), statement.rowDesc); + decoder = new RowResultDecoder<>(cmd.collector(), statement.rowDesc, encoder.socketConnection.getDataTypeCodecRegistry()); sendStatementFetchCommand(statement.statementId, cmd.fetch()); } else { Tuple params = cmd.params(); @@ -96,7 +97,7 @@ void decodePayload(ByteBuf payload, int payloadLength) { // need to reset packet number so that we can send a fetch request sequenceId = 0; // send fetch after cursor opened - decoder = new RowResultDecoder<>(cmd.collector(), statement.rowDesc); + decoder = new RowResultDecoder<>(cmd.collector(), statement.rowDesc, encoder.socketConnection.getDataTypeCodecRegistry()); statement.isCursorOpen = true; @@ -146,7 +147,7 @@ private void sendStatementExecuteCommand(MySQLPreparedStatement statement, boole for (int i = 0; i < numOfParams; i++) { Object value = params.getValue(i); if (value != null) { - DataTypeCodec.encodeBinary(statement.bindingTypes()[i], value, encoder.encodingCharset, packet); + encodeRowValue(packet, value, value.getClass()); } else { nullBitmap[i / 8] |= (1 << (i & 7)); } @@ -163,6 +164,13 @@ private void sendStatementExecuteCommand(MySQLPreparedStatement statement, boole sendPacket(packet, payloadLength); } + @SuppressWarnings("unchecked") + private void encodeRowValue(ByteBuf buf, T value, Class clazz) { + MySQLDataTypeCodecRegistry dataTypeCodecRegistry = encoder.socketConnection.getDataTypeCodecRegistry(); + io.vertx.sqlclient.codec.DataTypeCodec dataTypeCodec = (io.vertx.sqlclient.codec.DataTypeCodec) dataTypeCodecRegistry.lookupForEncoding(clazz); + dataTypeCodec.encode(buf, value); + } + private void sendStatementFetchCommand(long statementId, int count) { ByteBuf packet = allocateBuffer(); // encode packet header diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/QueryCommandBaseCodec.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/QueryCommandBaseCodec.java index 976f47c72..85c7d3b75 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/QueryCommandBaseCodec.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/QueryCommandBaseCodec.java @@ -94,7 +94,7 @@ protected void handleResultsetColumnDefinitions(ByteBuf payload) { protected void handleResultsetColumnDefinitionsDecodingCompleted() { commandHandlerState = CommandHandlerState.HANDLING_ROW_DATA_OR_END_PACKET; - decoder = new RowResultDecoder<>(cmd.collector(), /*cmd.isSingleton()*/ new MySQLRowDesc(columnDefinitions, format)); + decoder = new RowResultDecoder<>(cmd.collector(), /*cmd.isSingleton()*/ new MySQLRowDesc(columnDefinitions, format), encoder.socketConnection.getDataTypeCodecRegistry()); } protected void handleRows(ByteBuf payload, int payloadLength) { diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/RowResultDecoder.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/RowResultDecoder.java index f98a05717..2d5d9f383 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/RowResultDecoder.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/codec/RowResultDecoder.java @@ -12,13 +12,16 @@ package io.vertx.mysqlclient.impl.codec; import io.netty.buffer.ByteBuf; +import io.vertx.mysqlclient.impl.MySQLCollation; import io.vertx.mysqlclient.impl.MySQLRowDesc; import io.vertx.mysqlclient.impl.MySQLRowImpl; import io.vertx.mysqlclient.impl.datatype.DataFormat; -import io.vertx.mysqlclient.impl.datatype.DataType; -import io.vertx.mysqlclient.impl.datatype.DataTypeCodec; import io.vertx.mysqlclient.impl.protocol.ColumnDefinition; +import io.vertx.mysqlclient.impl.util.BufferUtils; +import io.vertx.mysqlclient.typecodec.MySQLDataTypeCodecRegistry; +import io.vertx.mysqlclient.typecodec.MySQLType; import io.vertx.sqlclient.Row; +import io.vertx.sqlclient.codec.DataTypeCodec; import io.vertx.sqlclient.impl.RowDecoder; import java.util.stream.Collector; @@ -27,10 +30,12 @@ class RowResultDecoder extends RowDecoder { private static final int NULL = 0xFB; MySQLRowDesc rowDesc; + MySQLDataTypeCodecRegistry dataTypeCodecRegistry; - RowResultDecoder(Collector collector, MySQLRowDesc rowDesc) { + RowResultDecoder(Collector collector, MySQLRowDesc rowDesc, MySQLDataTypeCodecRegistry dataTypeCodecRegistry) { super(collector); this.rowDesc = rowDesc; + this.dataTypeCodecRegistry = dataTypeCodecRegistry; } @Override @@ -55,10 +60,12 @@ protected Row decodeRow(int len, ByteBuf in) { if (nullByte == 0) { // non-null ColumnDefinition columnDef = rowDesc.columnDefinitions()[c]; - DataType dataType = columnDef.type(); + int type = columnDef.type(); int collationId = rowDesc.columnDefinitions()[c].characterSet(); int columnDefinitionFlags = columnDef.flags(); - decoded = DataTypeCodec.decodeBinary(dataType, collationId, columnDefinitionFlags, in); + + // data type codec decoding + decoded = decodeBinaryRowValue(type, collationId, columnDefinitionFlags, in); } row.addValue(decoded); } @@ -69,15 +76,79 @@ protected Row decodeRow(int len, ByteBuf in) { if (in.getUnsignedByte(in.readerIndex()) == NULL) { in.skipBytes(1); } else { - DataType dataType = rowDesc.columnDefinitions()[c].type(); + int type = rowDesc.columnDefinitions()[c].type(); int columnDefinitionFlags = rowDesc.columnDefinitions()[c].flags(); int collationId = rowDesc.columnDefinitions()[c].characterSet(); - decoded = DataTypeCodec.decodeText(dataType, collationId, columnDefinitionFlags, in); + decoded = decodeTextualRowValue(type, collationId, columnDefinitionFlags, in); } row.addValue(decoded); } } return row; } + + + private Object decodeBinaryRowValue(int type, int collationId, int columnDefinitionFlags, ByteBuf buffer) { + MySQLType mySQLType; + DataTypeCodec dataTypeCodec; + long len; + + switch (type) { + case ColumnDefinition.ColumnType.MYSQL_TYPE_TINY: + mySQLType = isUnsignedNumeric(columnDefinitionFlags) ? MySQLType.UNSIGNED_TINYINT : MySQLType.TINYINT; + len = 1; + break; + case ColumnDefinition.ColumnType.MYSQL_TYPE_STRING: + mySQLType = MySQLType.STRING; + len = BufferUtils.readLengthEncodedInteger(buffer); + break; + //TODO many other types implementation + default: + mySQLType = MySQLType.UNKNOWN; + len = BufferUtils.readLengthEncodedInteger(buffer); + break; + } + try { + dataTypeCodec = dataTypeCodecRegistry.registries().get(mySQLType.identifier()); + } catch (Exception ex) { + // log + return null; + } + + + return dataTypeCodec.binaryDecode(buffer, buffer.readerIndex(), len, MySQLCollation.getJavaCharsetByCollationId(collationId)); + } + + private Object decodeTextualRowValue(int type, int collationId, int columnDefinitionFlags, ByteBuf buffer) { + MySQLType mySQLType; + DataTypeCodec dataTypeCodec; + long len = BufferUtils.readLengthEncodedInteger(buffer); + + switch (type) { + case ColumnDefinition.ColumnType.MYSQL_TYPE_TINY: + mySQLType = isUnsignedNumeric(columnDefinitionFlags) ? MySQLType.UNSIGNED_TINYINT : MySQLType.TINYINT; + break; + case ColumnDefinition.ColumnType.MYSQL_TYPE_STRING: + mySQLType = MySQLType.STRING; + break; + //TODO many other types implementation + default: + mySQLType = MySQLType.UNKNOWN; + break; + } + try { + dataTypeCodec = dataTypeCodecRegistry.registries().get(mySQLType.identifier()); + } catch (Exception ex) { + // log + return null; + } + + return dataTypeCodec.textualDecode(buffer, buffer.readerIndex(), len, MySQLCollation.getJavaCharsetByCollationId(collationId)); + } + + private static boolean isUnsignedNumeric(int columnDefinitionFlags) { + return (columnDefinitionFlags & ColumnDefinition.ColumnDefinitionFlags.UNSIGNED_FLAG) != 0; + } + } diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/protocol/ColumnDefinition.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/protocol/ColumnDefinition.java index 0f557f6c1..13380abb3 100644 --- a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/protocol/ColumnDefinition.java +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/impl/protocol/ColumnDefinition.java @@ -28,7 +28,8 @@ public final class ColumnDefinition implements ColumnDescriptor { private final String orgName; private final int characterSet; private final long columnLength; - private final DataType type; + private final int type; + private final JDBCType jdbcType; private final int flags; private final byte decimals; @@ -40,7 +41,7 @@ public ColumnDefinition(String catalog, String orgName, int characterSet, long columnLength, - DataType type, + int type, int flags, byte decimals) { this.catalog = catalog; @@ -52,6 +53,7 @@ public ColumnDefinition(String catalog, this.characterSet = characterSet; this.columnLength = columnLength; this.type = type; + this.jdbcType = DataType.valueOf(type).jdbcType; this.flags = flags; this.decimals = decimals; } @@ -88,7 +90,7 @@ public long columnLength() { return columnLength; } - public DataType type() { + public int type() { return type; } @@ -108,7 +110,7 @@ public boolean isArray() { @Override public JDBCType jdbcType() { - return type.jdbcType; + return jdbcType; } @Override diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeCodecRegistry.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeCodecRegistry.java new file mode 100644 index 000000000..2300c9aaa --- /dev/null +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeCodecRegistry.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.mysqlclient.typecodec; + +import io.netty.util.collection.IntObjectHashMap; +import io.vertx.sqlclient.codec.DataType; +import io.vertx.sqlclient.codec.DataTypeCodec; +import io.vertx.sqlclient.codec.DataTypeCodecRegistry; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class MySQLDataTypeCodecRegistry implements DataTypeCodecRegistry { + + private final IntObjectHashMap> registries = new IntObjectHashMap<>(); + private final Map, DataTypeCodec> encodingTypeMapping = new HashMap<>(); + + public static final MySQLDataTypeCodecRegistry INSTANCE = new MySQLDataTypeCodecRegistry(); + + private MySQLDataTypeCodecRegistry() { + registries.put(MySQLType.TINYINT.identifier(), MySQLDataTypeDefaultCodecs.TinyIntTypeCodec.INSTANCE); + registries.put(MySQLType.UNSIGNED_TINYINT.identifier(), MySQLDataTypeDefaultCodecs.UnsignedTinyIntCodec.INSTANCE); + + encodingTypeMapping.put(Byte.class, MySQLDataTypeDefaultCodecs.TinyIntTypeCodec.INSTANCE); + } + + @Override + public void register(DataTypeCodec dataTypeCodec) { + DataType dataType = dataTypeCodec.dataType(); + if (dataType == null) { + throw new IllegalArgumentException(); // TODO errmsg + } + DataTypeCodec currentCodec = registries.get(dataType.identifier()); + if (currentCodec != null) { + throw new IllegalArgumentException(); // TODO errmsg + } + + Class encodingJavaClass = dataType.encodingJavaClass(); + DataTypeCodec encodingCodec = encodingTypeMapping.get(encodingJavaClass); + if (encodingCodec != null) { + // TODO warn msg + // this will override the default codec + encodingTypeMapping.put(encodingJavaClass, encodingCodec); + } + + registries.put(dataType.identifier(), dataTypeCodec); + } + + @Override + public void unregister(DataTypeCodec dataTypeCodec) { + DataType dataType = dataTypeCodec.dataType(); + DataTypeCodec codec = registries.get(dataType.identifier()); + if (codec == null) { + throw new IllegalArgumentException(); // TODO errmsg + } + + Class encodingJavaClass = dataType.encodingJavaClass(); + DataTypeCodec encodingCodec = encodingTypeMapping.get(encodingJavaClass); + if (encodingCodec == dataTypeCodec) { + // TODO warn msg + // this will remove the default encoding codec + encodingTypeMapping.remove(encodingJavaClass); + } + + registries.remove(dataType.identifier()); + } + + @Override + @SuppressWarnings("unchecked") + public DataTypeCodec lookup(DataType dataType) { + DataTypeCodec dataTypeCodec = registries.get(dataType.identifier()); + if (dataTypeCodec == null) { + throw new RuntimeException(); // TODO errmsg + } + return (DataTypeCodec) dataTypeCodec; + } + + public IntObjectHashMap> registries() { + return registries; + } + + @SuppressWarnings("unchecked") + public DataTypeCodec lookupForEncoding(Class clazz) { + DataTypeCodec dataTypeCodec = (DataTypeCodec) encodingTypeMapping.get(clazz); + if (dataTypeCodec == null) { + throw new RuntimeException(); //TODO errmsg + } + return dataTypeCodec; + } + + @Override + public Collection> listAll() { + return registries.values(); + } +} diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeDefaultCodecs.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeDefaultCodecs.java new file mode 100644 index 000000000..ae0816ec3 --- /dev/null +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLDataTypeDefaultCodecs.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.mysqlclient.typecodec; + +import io.netty.buffer.ByteBuf; +import io.vertx.sqlclient.codec.DataType; +import io.vertx.sqlclient.codec.DataTypeCodec; +import io.vertx.sqlclient.impl.codec.CommonCodec; + +import java.nio.charset.Charset; +import java.sql.JDBCType; + +// make this feature optional via configuration to avoid megamorphic virtual calls? +public class MySQLDataTypeDefaultCodecs { + public static class TinyIntTypeCodec implements DataTypeCodec { + + public static final TinyIntTypeCodec INSTANCE = new TinyIntTypeCodec(); + + private static final DataType TINYINT_DATATYPE = new DataType() { + @Override + public int identifier() { + return MySQLType.TINYINT.identifier(); + } + + @Override + public JDBCType jdbcType() { + return JDBCType.TINYINT; + } + + @Override + public Class encodingJavaClass() { + return Byte.class; + } + + @Override + public Class decodingJavaClass() { + return Byte.class; + } + }; + + private TinyIntTypeCodec() { + } + + @Override + public DataType dataType() { + return TINYINT_DATATYPE; + } + + @Override + public void encode(ByteBuf buffer, Byte value) { + buffer.writeByte(value); + } + + @Override + public Byte binaryDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + return buffer.readByte(); + } + + @Override + public Byte textualDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + return (byte) CommonCodec.decodeDecStringToLong(readerIndex, (int) length, buffer); + } + + } + + public static class UnsignedTinyIntCodec implements DataTypeCodec { + + public static final UnsignedTinyIntCodec INSTANCE = new UnsignedTinyIntCodec(); + + private static final DataType UNSIGNEDINT_DATATYPE = new DataType() { + @Override + public int identifier() { + return MySQLType.UNSIGNED_TINYINT.identifier(); + } + + @Override + public JDBCType jdbcType() { + return JDBCType.TINYINT; + } + + @Override + public Class encodingJavaClass() { + return Short.class; + } + + @Override + public Class decodingJavaClass() { + return Short.class; + } + }; + + @Override + public DataType dataType() { + return UNSIGNEDINT_DATATYPE; + } + + @Override + public void encode(ByteBuf buffer, Short value) { + buffer.writeShortLE(value); + } + + @Override + public Short textualDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + return (short) CommonCodec.decodeDecStringToLong(readerIndex, (int) length, buffer); + } + + @Override + public Short binaryDecode(ByteBuf buffer, int readerIndex, long length, Charset charset) { + return buffer.readShortLE(); + } + } + + //TODO there are many classes to implement... +} diff --git a/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLType.java b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLType.java new file mode 100644 index 000000000..f67199ad6 --- /dev/null +++ b/vertx-mysql-client/src/main/java/io/vertx/mysqlclient/typecodec/MySQLType.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.mysqlclient.typecodec; + +public enum MySQLType { + UNKNOWN(0x00), // this is for internal use + TINYINT(0x01), + UNSIGNED_TINYINT(0x02), + SMALLINT(0x03), + UNSIGNED_SMALLINT(0x04), + MEDIUMINT(0x05), + UNSIGNED_MEDIUMINT(0x06), + INT(0x07), + UNSIGNED_INT(0x08), + BIGINT(0x09), + UNSIGNED_BIGINT(0x10), + DOUBLE(0x11), + FLOAT(0x12), + BIT(0x13), + NUMERIC(0x14), + + DATE(0x15), + TIME(0x16), + DATETIME(0x17), + TIMESTAMP(0x18), + YEAR(0x19), + + STRING(0x20), + BINARY_STRING(0x21), + VARSTRING(0x22), + BINARY_VARSTRING(0x23), + TEXT(0x24), + BLOB(0x25), + + JSON(0x26), + GEOMETRY(0x27); + + private final int identifier; + + MySQLType(int identifier) { + this.identifier = identifier; + } + + public int identifier() { + return identifier; + } +} diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/SqlConnection.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/SqlConnection.java index 39a31e9d8..afe366086 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/SqlConnection.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/SqlConnection.java @@ -22,6 +22,7 @@ import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; +import io.vertx.sqlclient.codec.DataTypeCodecRegistry; import io.vertx.sqlclient.spi.DatabaseMetadata; /** @@ -89,10 +90,11 @@ public interface SqlConnection extends SqlClient { * @param handler the completion handler */ void close(Handler> handler); - + /** * @return The static metadata about the backend database server for this connection */ DatabaseMetadata databaseMetadata(); + DataTypeCodecRegistry dataTypeCodecRegistry(); } diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataType.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataType.java new file mode 100644 index 000000000..bf6829a3c --- /dev/null +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataType.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.sqlclient.codec; + +import java.sql.JDBCType; + +public interface DataType { + int identifier(); + + JDBCType jdbcType(); + + Class encodingJavaClass(); + + Class
decodingJavaClass(); + + +} diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodec.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodec.java new file mode 100644 index 000000000..c83edea21 --- /dev/null +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodec.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.sqlclient.codec; + +import io.netty.buffer.ByteBuf; + +import java.nio.charset.Charset; + +public interface DataTypeCodec { + DataType dataType(); + + void encode(ByteBuf buffer, ET value); + + DT textualDecode(ByteBuf buffer, int readerIndex, long length, Charset charset); + + DT binaryDecode(ByteBuf buffer, int readerIndex, long length, Charset charset); + +} diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodecRegistry.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodecRegistry.java new file mode 100644 index 000000000..8467cc647 --- /dev/null +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/codec/DataTypeCodecRegistry.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.sqlclient.codec; + +import java.util.Collection; + +public interface DataTypeCodecRegistry { + void register(DataTypeCodec dataTypeCodec); + + void unregister(DataTypeCodec dataTypeCodec); + + DataTypeCodec lookup(DataType dataType); + + Collection> listAll(); +} diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/Connection.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/Connection.java index 9e99a6e3c..48258c2e3 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/Connection.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/Connection.java @@ -18,6 +18,7 @@ package io.vertx.sqlclient.impl; import io.vertx.core.Promise; +import io.vertx.sqlclient.codec.DataTypeCodecRegistry; import io.vertx.sqlclient.impl.command.CommandScheduler; import io.vertx.sqlclient.spi.DatabaseMetadata; @@ -36,6 +37,8 @@ default boolean isIndeterminatePreparedStatementError(Throwable error) { DatabaseMetadata getDatabaseMetaData(); + DataTypeCodecRegistry getDataTypeCodecRegistry(); + void close(Holder holder, Promise promise); int getProcessId(); diff --git a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/SqlConnectionImpl.java b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/SqlConnectionImpl.java index d0c8e8a5f..51e845bfc 100644 --- a/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/SqlConnectionImpl.java +++ b/vertx-sql-client/src/main/java/io/vertx/sqlclient/impl/SqlConnectionImpl.java @@ -21,6 +21,7 @@ import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.spi.metrics.ClientMetrics; import io.vertx.sqlclient.SqlConnection; +import io.vertx.sqlclient.codec.DataTypeCodecRegistry; import io.vertx.sqlclient.impl.command.CommandBase; import io.vertx.sqlclient.Transaction; @@ -90,6 +91,11 @@ public DatabaseMetadata databaseMetadata() { return conn.getDatabaseMetaData(); } + @Override + public DataTypeCodecRegistry dataTypeCodecRegistry() { + return conn.getDataTypeCodecRegistry(); + } + @Override public C closeHandler(Handler handler) { closeHandler = handler;