diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/BinaryDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/BinaryDataType.java deleted file mode 100644 index 2917cbbb5..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/BinaryDataType.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.mssqlclient.impl.codec; - -import io.vertx.core.buffer.Buffer; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataType; - -import java.sql.JDBCType; - -public class BinaryDataType extends MSSQLDataType { - - private final int length; - - public BinaryDataType(int id, int length) { - super(id, Buffer.class, JDBCType.BINARY); - this.length = length; - } - - public int getLength() { - return length; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java index 10b3d8569..29f081f81 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/CloseStatementCommandCodec.java @@ -17,11 +17,12 @@ import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId; import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; import io.vertx.sqlclient.impl.command.CloseStatementCommand; import io.vertx.sqlclient.impl.command.CommandResponse; +import static io.vertx.mssqlclient.impl.codec.DataType.INTN; + class CloseStatementCommandCodec extends MSSQLCommandCodec { CloseStatementCommandCodec(CloseStatementCommand cmd) { @@ -93,7 +94,7 @@ private void sendUnprepareRequest() { // Option flags packet.writeShortLE(0x0000); - encodeIntNParameter(packet, ((MSSQLPreparedStatement) cmd.statement()).handle); + INTN.encodeParam(packet, null, false, ((MSSQLPreparedStatement) cmd.statement()).handle); int packetLen = packet.writerIndex() - packetLenIdx + 2; packet.setShort(packetLenIdx, packetLen); @@ -107,13 +108,4 @@ protected void encodeTransactionDescriptor(ByteBuf payload, long transactionDesc payload.writeLongLE(transactionDescriptor); payload.writeIntLE(outstandingRequestCount); } - - private void encodeIntNParameter(ByteBuf payload, Object value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - payload.writeByte(4); - payload.writeByte(4); - payload.writeIntLE((Integer) value); - } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java index 9442ebf07..604b8d5e7 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ColumnData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * 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 @@ -11,53 +11,33 @@ package io.vertx.mssqlclient.impl.codec; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataType; import io.vertx.sqlclient.desc.ColumnDescriptor; import java.sql.JDBCType; public final class ColumnData implements ColumnDescriptor { - /* - Protocol reference: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/58880b9f-381c-43b2-bf8b-0727a98c4f4c - */ - private final long usertype; - private final int flags; - private final MSSQLDataType dataType; - private final String colName; - // CryptoMetaData support? - String tableName; + private final String name; + private final DataType dataType; + private final DataType.Metadata metadata; - public ColumnData(long usertype, int flags, MSSQLDataType dataType, String colName) { - this.usertype = usertype; - this.flags = flags; + public ColumnData(String name, DataType dataType, DataType.Metadata metadata) { this.dataType = dataType; - this.colName = colName; + this.name = name; + this.metadata = metadata; } - public long usertype() { - return usertype; - } - - public int flags() { - return flags; - } - - public MSSQLDataType dataType() { + public DataType dataType() { return dataType; } - public String colName() { - return colName; - } - - public String tableName() { - return tableName; + public DataType.Metadata metadata() { + return metadata; } @Override public String name() { - return colName; + return name; } @Override @@ -67,22 +47,6 @@ public boolean isArray() { @Override public JDBCType jdbcType() { - return dataType.jdbcType(); - } - - public static final class Flags { - public static final int NULLABLE = 0x0001; - public static final int CASESEN = 0x0002; - public static final int UPDATEABLE = 0x0004; - public static final int IDENTITY = 0x0010; - public static final int COMPUTED = 0x0020; - // 2-BIT RESERVED for ODBC - public static final int FIXED_LEN_CLR_TYPE = 0x0100; - // 1-BIT RESERVED - public static final int SPARSE_COLUMN_SET = 0x0400; - public static final int ENCRYPTED = 0x0800; - public static final int HIDDEN = 0x2000; - public static final int KEY = 0x4000; - public static final int NULLABLE_UNKNOWN = 0x8000; + return dataType.jdbcType(metadata); } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java new file mode 100644 index 000000000..8747c6e7c --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/DataType.java @@ -0,0 +1,863 @@ +/* + * 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.mssqlclient.impl.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.collection.IntObjectHashMap; +import io.netty.util.collection.IntObjectMap; +import io.vertx.core.buffer.Buffer; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.sql.JDBCType; +import java.time.*; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.*; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +public enum DataType { + + // Zero-Length Data Types https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/bc91c82f-8ee0-4256-98d9-c800bf9ae33b + NULL(0x1F) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public String paramDefinition(Object value) { + return "nvarchar(4000)"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + } + }, + + // Fixed-Length Data Types https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/859eb3d2-80d3-40f6-a637-414552c9c552 + INT1(0x30) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TINYINT; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readUnsignedByte(); + } + }, + BIT(0x32) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BOOLEAN; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readBoolean(); + } + }, + INT2(0x34) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.SMALLINT; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readShortLE(); + } + }, + INT4(0x38) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.INTEGER; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readIntLE(); + } + }, + DATETIM4(0x3A), + FLT4(0x3B) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.REAL; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readFloatLE(); + } + }, + MONEY(0x3C), + DATETIME(0x3D) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIMESTAMP; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + LocalDate localDate = START_DATE_DATETIME.plus(byteBuf.readIntLE(), ChronoUnit.DAYS); + long nanoOfDay = NANOSECONDS.convert(Math.round(byteBuf.readIntLE() * (3 + 1D / 3)), MILLISECONDS); + LocalTime localTime = LocalTime.ofNanoOfDay(nanoOfDay); + return LocalDateTime.of(localDate, localTime); + } + }, + FLT8(0x3E) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DOUBLE; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readDoubleLE(); + } + }, + MONEY4(0x7A), + INT8(0x7F) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BIGINT; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return byteBuf.readLongLE(); + } + }, + DECIMAL(0x37), + NUMERIC(0x3F), + + // Variable-Length Data Types https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/ce3183a6-9d89-47e8-a02f-de5a1a1303de + GUID(0x24), + INTN(0x26) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + if (metadata.length == 1) return JDBCType.TINYINT; + if (metadata.length == 2) return JDBCType.SMALLINT; + if (metadata.length == 4) return JDBCType.INTEGER; + if (metadata.length == 8) return JDBCType.BIGINT; + throw new IllegalArgumentException("Invalid length: " + metadata.length); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + if (length == 1) return byteBuf.readUnsignedByte(); + if (length == 2) return byteBuf.readShortLE(); + if (length == 4) return byteBuf.readIntLE(); + if (length == 8) return byteBuf.readLongLE(); + throw new IllegalArgumentException("Invalid length: " + length); + } + + @Override + public String paramDefinition(Object value) { + return "bigint"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + if (value instanceof Byte) { + Byte bValue = (Byte) value; + byteBuf.writeByte(1); // max length + byteBuf.writeByte(1); // actual length + byteBuf.writeByte(bValue); + } else if (value instanceof Short) { + Short sValue = (Short) value; + byteBuf.writeByte(2); // max length + byteBuf.writeByte(2); // actual length + byteBuf.writeShortLE(sValue); + } else if (value instanceof Integer) { + Integer iValue = (Integer) value; + byteBuf.writeByte(4); // max length + byteBuf.writeByte(4); // actual length + byteBuf.writeIntLE(iValue); + } else if (value instanceof Long) { + Long lValue = (Long) value; + byteBuf.writeByte(8); // max length + byteBuf.writeByte(8); // actual length + byteBuf.writeLongLE(lValue); + } else throw new IllegalArgumentException(value.getClass().getName()); + } + }, + BITN(0x68) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BOOLEAN; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + if (length == 1) return byteBuf.readBoolean(); + throw new IllegalArgumentException("Invalid length: " + length); + } + + @Override + public String paramDefinition(Object value) { + return "bit"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, BITN.id); + byteBuf.writeByte(1); // max length + byteBuf.writeByte(1); // actual length + byteBuf.writeBoolean((Boolean) value); + } + }, + DECIMALN(0x6A) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedByte(); + metadata.precision = byteBuf.readByte(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DECIMAL; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + short length = byteBuf.readUnsignedByte(); + if (length == 0) return null; + byte sign = byteBuf.readByte(); + byte[] bytes = new byte[length - 1]; + for (int i = 0; i < bytes.length; i++) bytes[i] = byteBuf.getByte(byteBuf.readerIndex() + bytes.length - 1 - i); + byteBuf.skipBytes(bytes.length); + BigInteger bigInteger = new BigInteger(bytes); + BigDecimal bigDecimal = new BigDecimal(bigInteger, metadata.scale); + return sign == 0 ? bigDecimal.negate() : bigDecimal; + } + + @Override + public String paramDefinition(Object value) { + return "numeric(38," + (value == null ? 0 : Math.max(0, ((BigDecimal) value).scale())) + ")"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + BigDecimal bigDecimal = (BigDecimal) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(17); // maximum length + byteBuf.writeByte(38); // maximum precision + int sign = bigDecimal.signum() < 0 ? 0 : 1; + byte[] bytes = (sign == 0 ? bigDecimal.negate() : bigDecimal).unscaledValue().toByteArray(); + byteBuf.writeByte(Math.max(0, bigDecimal.scale())); + byteBuf.writeByte(1 + bytes.length); + byteBuf.writeByte(sign); + for (int i = bytes.length - 1; i >= 0; i--) byteBuf.writeByte(bytes[i]); + } + }, + NUMERICN(0x6C) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return DECIMALN.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DECIMAL; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return DECIMALN.decodeValue(byteBuf, metadata); + } + }, + FLTN(0x6D) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + if (metadata.length == 4) return JDBCType.REAL; + if (metadata.length == 8) return JDBCType.DOUBLE; + throw new IllegalArgumentException("Invalid length: " + metadata.length); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + if (length == 4) return byteBuf.readFloatLE(); + if (length == 8) return byteBuf.readDoubleLE(); + throw new IllegalArgumentException("Invalid length: " + length); + } + + @Override + public String paramDefinition(Object value) { + return "float"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + if (value instanceof Float) { + Float fValue = (Float) value; + byteBuf.writeByte(4); // max length + byteBuf.writeByte(4); // actual length + byteBuf.writeFloatLE(fValue); + } else if (value instanceof Double) { + Double dValue = (Double) value; + byteBuf.writeByte(8); // max length + byteBuf.writeByte(8); // actual length + byteBuf.writeDoubleLE(dValue); + } else throw new IllegalArgumentException(); + } + }, + MONEYN(0x6E), + DATETIMN(0x6F), + DATEN(0x28) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return null; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.DATE; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + return decodeLocalDate(byteBuf, length); + } + + @Override + public String paramDefinition(Object value) { + return "date"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(3); + byteBuf.writeMediumLE(daysFromStartDate((LocalDate) value)); + } + }, + TIMEN(0x29) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIME; + } + + @Override + public String paramDefinition(Object value) { + return "time"; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readByte(); + if (length == 0) return null; + return decodeLocalTime(byteBuf, length, metadata.scale); + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(7); // scale + byteBuf.writeByte(5); // length + writeUnsignedInt40LE(byteBuf, hundredsOfNanos((LocalTime) value)); + } + }, + DATETIME2N(0x2A) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIMESTAMP; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + byte length = byteBuf.readByte(); + if (length == 0) return null; + LocalTime localTime = decodeLocalTime(byteBuf, length - 3, metadata.scale); + LocalDate localDate = decodeLocalDate(byteBuf, 3); + return LocalDateTime.of(localDate, localTime); + } + + @Override + public String paramDefinition(Object value) { + return "datetime2"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + LocalDateTime localDateTime = (LocalDateTime) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(7); // scale + byteBuf.writeByte(8); // length + writeUnsignedInt40LE(byteBuf, hundredsOfNanos(localDateTime.toLocalTime())); + byteBuf.writeMediumLE(daysFromStartDate(localDateTime.toLocalDate())); + } + }, + DATETIMEOFFSETN(0x2B) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.scale = byteBuf.readByte(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.TIMESTAMP_WITH_TIMEZONE; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + byte length = byteBuf.readByte(); + if (length == 0) return null; + LocalTime localTime = decodeLocalTime(byteBuf, length - 5, metadata.scale); + LocalDate localDate = decodeLocalDate(byteBuf, 3); + short minutes = byteBuf.readShortLE(); + return LocalDateTime.of(localDate, localTime).plusMinutes(minutes).atOffset(ZoneOffset.ofTotalSeconds(60 * minutes)); + } + + @Override + public String paramDefinition(Object value) { + return "datetimeoffset"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + OffsetDateTime offsetDateTime = (OffsetDateTime) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeByte(7); // scale + byteBuf.writeByte(10); // length + int offsetMinutes = offsetDateTime.getOffset().getTotalSeconds() / 60; + LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(offsetMinutes); + writeUnsignedInt40LE(byteBuf, hundredsOfNanos(localDateTime.toLocalTime())); + byteBuf.writeMediumLE(daysFromStartDate(localDateTime.toLocalDate())); + byteBuf.writeShortLE(offsetMinutes); + } + }, + CHAR(0x2F), + VARCHAR(0x27), + BINARY(0x2D) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARBINARY.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARBINARY.decodeValue(byteBuf, metadata); + } + }, + VARBINARY(0x25) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARBINARY.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.VARBINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARBINARY.decodeValue(byteBuf, metadata); + } + }, + + BIGVARBINARY(0xA5) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedShortLE(); + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.LONGVARBINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readUnsignedShortLE(); + ByteBuf res = Unpooled.buffer(length); + byteBuf.readBytes(res, 0, length); + res.writerIndex(length); + return Buffer.buffer(res); + } + + @Override + public String paramDefinition(Object value) { + return "binary(" + (value == null ? 1 : ((Buffer) value).length()) + ")"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + Buffer buffer = (Buffer) value; + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeShortLE(buffer.length()); // max length + byteBuf.writeShortLE(buffer.length()); // length + byteBuf.writeBytes(buffer.getByteBuf()); + } + }, + BIGVARCHAR(0xA7) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedShortLE(); + byteBuf.skipBytes(2); // skip collate codepage + byteBuf.skipBytes(2); // skip collate flags + byteBuf.skipBytes(1); // skip collate charset id + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.VARCHAR; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readUnsignedShortLE(); + // CHARBIN_NULL + if (length == 65535) return null; + return byteBuf.readCharSequence(length, StandardCharsets.UTF_8); + } + }, + BIGBINARY(0xAD) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARBINARY.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.BINARY; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARBINARY.decodeValue(byteBuf, metadata); + } + }, + BIGCHAR(0xAF) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return BIGVARCHAR.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return BIGVARCHAR.jdbcType(metadata); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return BIGVARCHAR.decodeValue(byteBuf, metadata); + } + }, + NVARCHAR(0xE7) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + Metadata metadata = new Metadata(); + metadata.length = byteBuf.readUnsignedShortLE(); + byteBuf.skipBytes(2); // skip collate codepage + byteBuf.skipBytes(2); // skip collate flags + byteBuf.skipBytes(1); // skip collate charset id + return metadata; + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return JDBCType.VARCHAR; + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + int length = byteBuf.readUnsignedShortLE(); + // CHARBIN_NULL + if (length == 65535) return null; + return byteBuf.readCharSequence(length, StandardCharsets.UTF_16LE); + } + + @Override + public String paramDefinition(Object value) { + return "nvarchar(4000)"; + } + + @Override + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + writeParamDescription(byteBuf, name, out, id); + byteBuf.writeShortLE(8000); // maximal length + byteBuf.writeByte(0x09); + byteBuf.writeByte(0x04); + byteBuf.writeByte(0xd0); + byteBuf.writeByte(0x00); + byteBuf.writeByte(0x34); // Collation for param definitions TODO always this value? + writeUnsignedShortLengthString(byteBuf, value instanceof Enum ? ((Enum) value).name() : value.toString()); + } + }, + NCHAR(0xEF) { + @Override + public Metadata decodeMetadata(ByteBuf byteBuf) { + return NVARCHAR.decodeMetadata(byteBuf); + } + + @Override + public JDBCType jdbcType(Metadata metadata) { + return NVARCHAR.jdbcType(metadata); + } + + @Override + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + return NVARCHAR.decodeValue(byteBuf, metadata); + } + }, + XML(0xF1), + UDT(0xF0), + + TEXT(0x23), + IMAGE(0x22), + NTEXT(0x63), + SSVARIANT(0x62); + + public final int id; + + DataType(int id) { + this.id = id; + } + + public static class Metadata { + private int length; + private byte precision; + private byte scale; + + public int length() { + return length; + } + + public byte precision() { + return precision; + } + + public byte scale() { + return scale; + } + + @Override + public String toString() { + return "Metadata{" + "length=" + length + ", precision=" + precision + ", scale=" + scale + '}'; + } + } + + public Metadata decodeMetadata(ByteBuf byteBuf) { + throw new UnsupportedOperationException("Unable to decode metadata for " + name()); + } + + public JDBCType jdbcType(Metadata metadata) { + throw new UnsupportedOperationException("Unable to determine jdbc type for " + name()); + } + + public Object decodeValue(ByteBuf byteBuf, Metadata metadata) { + throw new UnsupportedOperationException("Unable to decode value for " + name()); + } + + public String paramDefinition(Object value) { + throw new UnsupportedOperationException("Unable to generate param definition for " + name()); + } + + public void encodeParam(ByteBuf byteBuf, String name, boolean out, Object value) { + throw new UnsupportedOperationException("Unable to encode param for " + name()); + } + + private static final LocalDate START_DATE = LocalDate.of(1, 1, 1); + private static final LocalDate START_DATE_DATETIME = LocalDate.of(1900, 1, 1); + private static final IntObjectMap typesById; + private static final Map, DataType> typesByValueClass; + + static { + typesById = new IntObjectHashMap<>(values().length); + for (DataType dataType : values()) typesById.put(dataType.id, dataType); + typesByValueClass = new HashMap<>(); + typesByValueClass.put(Byte.class, INTN); + typesByValueClass.put(Short.class, INTN); + typesByValueClass.put(Integer.class, INTN); + typesByValueClass.put(Long.class, INTN); + typesByValueClass.put(Boolean.class, BITN); + typesByValueClass.put(Float.class, FLTN); + typesByValueClass.put(Double.class, FLTN); + typesByValueClass.put(BigDecimal.class, DECIMALN); + typesByValueClass.put(String.class, NVARCHAR); + typesByValueClass.put(LocalDate.class, DATEN); + typesByValueClass.put(LocalTime.class, TIMEN); + typesByValueClass.put(LocalDateTime.class, DATETIME2N); + typesByValueClass.put(OffsetDateTime.class, DATETIMEOFFSETN); + typesByValueClass.put(Buffer.class, BIGVARBINARY); + } + + public static DataType forId(int id) { + DataType dataType = typesById.get(id); + if (dataType == null) throw new IllegalArgumentException("Unknown data type: " + id); + return dataType; + } + + public static DataType forValueClass(Class valueClass) { + DataType dataType; + if (Buffer.class.isAssignableFrom(valueClass)) { + dataType = typesByValueClass.get(Buffer.class); + } else if (valueClass.isEnum()) { + dataType = typesByValueClass.get(String.class); + } else { + dataType = typesByValueClass.get(valueClass); + } + if (dataType == null) { + throw new IllegalArgumentException("Unsupported value class: " + valueClass); + } + return dataType; + } + + private static void writeParamDescription(ByteBuf buffer, String name, boolean out, int id) { + writeByteLengthString(buffer, name); + buffer.writeByte(out ? 1 : 0); + buffer.writeByte(id); + } + + private static LocalDate decodeLocalDate(ByteBuf byteBuf, int length) { + int days; + if (length == 3) { + days = byteBuf.readUnsignedMediumLE(); + return START_DATE.plus(days, ChronoUnit.DAYS); + } + throw new IllegalArgumentException("Invalid length: " + length); + } + + private static LocalTime decodeLocalTime(ByteBuf byteBuf, int length, int scale) { + long hundredNanos; + if (length == 3) { + hundredNanos = byteBuf.readUnsignedMediumLE(); + } else if (length == 4) { + hundredNanos = byteBuf.readUnsignedIntLE(); + } else if (length == 5) { + hundredNanos = readUnsignedInt40LE(byteBuf); + } else { + throw new IllegalArgumentException("Invalid length: " + length); + } + for (int i = scale; i < 7; i++) { + hundredNanos *= 10; + } + return LocalTime.ofNanoOfDay(100 * hundredNanos); + } + + private static int daysFromStartDate(LocalDate localDate) { + return (int) ChronoUnit.DAYS.between(START_DATE, localDate); + } + + private static long hundredsOfNanos(LocalTime localTime) { + return localTime.toNanoOfDay() / 100; + } +} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java index 7386d6871..bf331eef1 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/ExtendedQueryCommandBaseCodec.java @@ -13,25 +13,17 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -import io.vertx.core.buffer.Buffer; import io.vertx.mssqlclient.impl.protocol.MessageStatus; import io.vertx.mssqlclient.impl.protocol.MessageType; import io.vertx.mssqlclient.impl.protocol.TdsMessage; import io.vertx.mssqlclient.impl.protocol.client.rpc.ProcId; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId; import io.vertx.mssqlclient.impl.protocol.server.DoneToken; import io.vertx.mssqlclient.impl.protocol.token.DataPacketStreamTokenType; +import io.vertx.sqlclient.data.NullValue; import io.vertx.sqlclient.impl.TupleInternal; import io.vertx.sqlclient.impl.command.ExtendedQueryCommand; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; - -import static io.vertx.mssqlclient.impl.codec.MSSQLDataTypeCodec.inferenceParamDefinitionByValueType; +import static io.vertx.mssqlclient.impl.codec.DataType.*; abstract class ExtendedQueryCommandBaseCodec extends QueryCommandBaseCodec> { @@ -159,27 +151,20 @@ private void sendPrepexecRequest() { // Parameter // OUT Parameter - packet.writeByte(0x00); - packet.writeByte(0x01); // By reference - packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - packet.writeByte(0x04); - packet.writeByte(0x04); MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; - packet.writeIntLE(ps.handle); + INTN.encodeParam(packet, null, true, ps.handle); TupleInternal params = prepexecRequestParams(); // Param definitions String paramDefinitions = parseParamDefinitions(params); - encodeNVarcharParameter(packet, paramDefinitions); + NVARCHAR.encodeParam(packet, null, false, paramDefinitions); // SQL text - encodeNVarcharParameter(packet, cmd.sql()); + NVARCHAR.encodeParam(packet, null, false, cmd.sql()); // Param values - for (int i = 0; i < params.size(); i++) { - encodeParamValue(packet, params.getValue(i)); - } + encodeParams(packet, params); int packetLen = packet.writerIndex() - packetLenIdx + 2; packet.setShort(packetLenIdx, packetLen); @@ -228,21 +213,11 @@ protected void writeRpcRequestBatch(ByteBuf packet) { // Parameter // OUT Parameter - packet.writeByte(0x00); - packet.writeByte(0x00); - packet.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - packet.writeByte(0x04); // Max length - packet.writeByte(0x04); // Length MSSQLPreparedStatement ps = (MSSQLPreparedStatement) cmd.ps; - packet.writeIntLE(ps.handle); - - TupleInternal params = execRequestParams(); + INTN.encodeParam(packet, null, true, ps.handle); // Param values - for (int i = 0; i < params.size(); i++) { - encodeParamValue(packet, params.getValue(i)); - } - + encodeParams(packet, execRequestParams()); } protected abstract TupleInternal execRequestParams(); @@ -250,226 +225,36 @@ protected void writeRpcRequestBatch(ByteBuf packet) { private String parseParamDefinitions(TupleInternal params) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < params.size(); i++) { - Object param = params.getValueInternal(i); - stringBuilder.append("@P").append(i + 1).append(" "); - stringBuilder.append(inferenceParamDefinitionByValueType(param)); - if (i != params.size() - 1) { + if (i > 0) { stringBuilder.append(","); } + stringBuilder.append("@P").append(i + 1).append(" "); + Object param = params.getValueInternal(i); + if (param == null) { + stringBuilder.append(NULL.paramDefinition(null)); + } else if (param instanceof NullValue) { + Class valueClass = ((NullValue) param).type(); + DataType dataType = forValueClass(valueClass); + stringBuilder.append(dataType.paramDefinition(null)); + } else { + Class valueClass = param.getClass(); + DataType dataType = forValueClass(valueClass); + stringBuilder.append(dataType.paramDefinition(param)); + } } return stringBuilder.toString(); } - private void encodeNVarcharParameter(ByteBuf payload, String value) { - payload.writeByte(0x00); // name length - payload.writeByte(0x00); // status flags - payload.writeByte(MSSQLDataTypeId.NVARCHARTYPE_ID); - payload.writeShortLE(8000); // maximal length - payload.writeByte(0x09); - payload.writeByte(0x04); - payload.writeByte(0xd0); - payload.writeByte(0x00); - payload.writeByte(0x34); // Collation for param definitions TODO always this value? - writeUnsignedShortLenVarChar(payload, value); - } - - private void encodeParamValue(ByteBuf payload, Object value) { - if (value == null) { - encodeNullParameter(payload); - } else if (value instanceof Byte) { - encodeIntNParameter(payload, 1, value); - } else if (value instanceof Short) { - encodeIntNParameter(payload, 2, value); - } else if (value instanceof Integer) { - encodeIntNParameter(payload, 4, value); - } else if (value instanceof Long) { - encodeIntNParameter(payload, 8, value); - } else if (value instanceof Float) { - encodeFloat4Parameter(payload, (Float) value); - } else if (value instanceof Double) { - encodeFloat8Parameter(payload, (Double) value); - } else if (value instanceof String) { - encodeNVarcharParameter(payload, (String) value); - } else if (value instanceof Enum) { - encodeNVarcharParameter(payload, ((Enum) value).name()); - } else if (value instanceof Boolean) { - encodeBitNParameter(payload, (Boolean) value); - } else if (value instanceof LocalDate) { - encodeDateNParameter(payload, (LocalDate) value); - } else if (value instanceof LocalTime) { - encodeTimeNParameter(payload, (LocalTime) value); - } else if (value instanceof LocalDateTime) { - encodeDateTimeNParameter(payload, (LocalDateTime) value); - } else if (value instanceof OffsetDateTime) { - encodeOffsetDateTimeNParameter(payload, (OffsetDateTime) value); - } else if (value instanceof BigDecimal) { - encodeDecimalParameter(payload, (BigDecimal) value); - } else if (value instanceof Buffer) { - encodeBufferParameter(payload, (Buffer) value); - } else { - throw new UnsupportedOperationException("Unsupported type"); - } - } - - private void encodeNullParameter(ByteBuf payload) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.NULLTYPE_ID); - } - - private void encodeIntNParameter(ByteBuf payload, int n, Object value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.INTNTYPE_ID); - payload.writeByte(n); - payload.writeByte(n); - switch (n) { - case 1: - payload.writeByte((Byte) value); - break; - case 2: - payload.writeShortLE((Short) value); - break; - case 4: - payload.writeIntLE((Integer) value); - break; - case 8: - payload.writeLongLE((Long) value); - break; - default: - throw new UnsupportedOperationException(); - } - } - - private void encodeBitNParameter(ByteBuf payload, Boolean bit) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.BITNTYPE_ID); - payload.writeByte(1); - payload.writeByte(1); - payload.writeBoolean(bit); - } - - private void encodeFloat4Parameter(ByteBuf payload, Float value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); - payload.writeByte(4); - payload.writeByte(4); - payload.writeFloatLE(value); - } - - private void encodeFloat8Parameter(ByteBuf payload, Double value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.FLTNTYPE_ID); - payload.writeByte(8); - payload.writeByte(8); - payload.writeDoubleLE(value); - } - - private void encodeDateNParameter(ByteBuf payload, LocalDate date) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATENTYPE_ID); - if (date == null) { - // null - payload.writeByte(0); - } else { - payload.writeByte(3); - encodeLocalDate(payload, date); - } - } - - private void encodeTimeNParameter(ByteBuf payload, LocalTime time) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.TIMENTYPE_ID); - - payload.writeByte(7); // scale - if (time == null) { - payload.writeByte(0); - } else { - payload.writeByte(5); // length - encodeLocalTime(payload, time); - } - } - - private void encodeDateTimeNParameter(ByteBuf payload, LocalDateTime dateTime) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATETIME2NTYPE_ID); - - payload.writeByte(7); // scale - if (dateTime == null) { - payload.writeByte(0); - } else { - payload.writeByte(8); // length - encodeLocalTime(payload, dateTime.toLocalTime()); - encodeLocalDate(payload, dateTime.toLocalDate()); - } - } - - private void encodeOffsetDateTimeNParameter(ByteBuf payload, OffsetDateTime offsetDateTime) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID); - - payload.writeByte(7); - if (offsetDateTime == null) { - payload.writeByte(0); - } else { - payload.writeByte(10); // length - int offsetMinutes = offsetDateTime.getOffset().getTotalSeconds() / 60; - LocalDateTime localDateTime = offsetDateTime.toLocalDateTime().minusMinutes(offsetMinutes); - encodeLocalTime(payload, localDateTime.toLocalTime()); - LocalDate localDate = localDateTime.toLocalDate(); - encodeLocalDate(payload, localDate); - payload.writeShortLE(offsetMinutes); - } - } - - private void encodeLocalTime(ByteBuf payload, LocalTime localTime) { - encodeInt40(payload, localTime.toNanoOfDay() / 100); - } - - private void encodeInt40(ByteBuf buffer, long value) { - buffer.writeIntLE((int) (value % 0x100000000L)); - buffer.writeByte((int) (value / 0x100000000L)); - } - - private void encodeLocalDate(ByteBuf payload, LocalDate localDate) { - long days = ChronoUnit.DAYS.between(MSSQLDataTypeCodec.START_DATE, localDate); - payload.writeMediumLE((int) days); - } - - private void encodeDecimalParameter(ByteBuf payload, BigDecimal value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.DECIMALNTYPE_ID); - - payload.writeByte(17); // maximum length - payload.writeByte(38); // maximum precision - - int sign = value.signum() < 0 ? 0 : 1; - byte[] bytes = (sign == 0 ? value.negate() : value).unscaledValue().toByteArray(); - - payload.writeByte(Math.max(0, value.scale())); - payload.writeByte(1 + bytes.length); - payload.writeByte(sign); - for (int i = bytes.length - 1; i >= 0; i--) { - payload.writeByte(bytes[i]); + private void encodeParams(ByteBuf buffer, TupleInternal params) { + for (int i = 0; i < params.size(); i++) { + String name = "@P" + (i + 1); + Object value = params.getValue(i); + if (value == null) { + NULL.encodeParam(buffer, name, false, null); + } else { + DataType dataType = DataType.forValueClass(value.getClass()); + dataType.encodeParam(buffer, name, false, value); + } } } - - private void encodeBufferParameter(ByteBuf payload, Buffer value) { - payload.writeByte(0x00); - payload.writeByte(0x00); - payload.writeByte(MSSQLDataTypeId.BIGBINARYTYPE_ID); - - payload.writeShortLE(value.length()); // max length - payload.writeShortLE(value.length()); // length - - payload.writeBytes(value.getByteBuf()); - } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java index a5930ec20..26f5048f7 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLCommandCodec.java @@ -22,7 +22,8 @@ import java.util.function.Consumer; -import static java.nio.charset.StandardCharsets.UTF_16LE; +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.readUnsignedByteLengthString; +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.readUnsignedShortLengthString; abstract class MSSQLCommandCodec> { final C cmd; @@ -52,9 +53,9 @@ void handleErrorToken(ByteBuf buffer) { int number = buffer.readIntLE(); byte state = buffer.readByte(); byte severity = buffer.readByte(); - String message = readUnsignedShortLenVarChar(buffer); - String serverName = readByteLenVarchar(buffer); - String procedureName = readByteLenVarchar(buffer); + String message = readUnsignedShortLengthString(buffer); + String serverName = readUnsignedByteLengthString(buffer); + String procedureName = readUnsignedByteLengthString(buffer); int lineNumber = buffer.readIntLE(); MSSQLException failure = new MSSQLException(number, state, severity, message, serverName, procedureName, lineNumber); @@ -76,18 +77,4 @@ void complete() { completionHandler.handle(resp); } - protected String readByteLenVarchar(ByteBuf buffer) { - int length = buffer.readUnsignedByte(); - return buffer.readCharSequence(length * 2, UTF_16LE).toString(); - } - - protected String readUnsignedShortLenVarChar(ByteBuf buffer) { - int length = buffer.readUnsignedShortLE(); - return buffer.readCharSequence(length * 2, UTF_16LE).toString(); - } - - protected void writeUnsignedShortLenVarChar(ByteBuf buffer, String value) { - buffer.writeShortLE(value.length() * 2); - buffer.writeCharSequence(value, UTF_16LE); - } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLDataTypeCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLDataTypeCodec.java deleted file mode 100644 index 8c550b05a..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLDataTypeCodec.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * 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.mssqlclient.impl.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.vertx.core.buffer.Buffer; -import io.vertx.mssqlclient.impl.protocol.datatype.*; -import io.vertx.sqlclient.data.NullValue; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.time.*; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Map; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.NANOSECONDS; - -class MSSQLDataTypeCodec { - static LocalDate START_DATE = LocalDate.of(1, 1, 1); - static LocalDate START_DATE_DATETIME = LocalDate.of(1900, 1, 1); - - private static final Map, String> parameterDefinitionsMapping = new HashMap<>(); - - static { - parameterDefinitionsMapping.put(Byte.class, "tinyint"); - parameterDefinitionsMapping.put(Short.class, "smallint"); - parameterDefinitionsMapping.put(Integer.class, "int"); - parameterDefinitionsMapping.put(Long.class, "bigint"); - parameterDefinitionsMapping.put(Boolean.class, "bit"); - parameterDefinitionsMapping.put(Float.class, "float"); - parameterDefinitionsMapping.put(Double.class, "float"); - parameterDefinitionsMapping.put(String.class, "nvarchar(4000)"); - parameterDefinitionsMapping.put(LocalDate.class, "date"); - parameterDefinitionsMapping.put(LocalTime.class, "time"); - parameterDefinitionsMapping.put(LocalDateTime.class, "datetime2(7)"); - parameterDefinitionsMapping.put(OffsetDateTime.class, "datetimeoffset(7)"); - } - - static String inferenceParamDefinitionByValueType(Object value) { - if (value == null) { - return "nvarchar(4000)"; - } - boolean nullValue = value instanceof NullValue; - Class type = nullValue ? ((NullValue) value).type() : value.getClass(); - if (type == BigDecimal.class) { - return "numeric(38," + (nullValue ? 0 : Math.max(0, ((BigDecimal) value).scale())) + ")"; - } else if (Buffer.class.isAssignableFrom(type)) { - return "binary(" + (nullValue ? 1 : ((Buffer) value).length()) + ")"; - } else if (type.isEnum()) { - return parameterDefinitionsMapping.get(String.class); - } else { - String paramDefinition = parameterDefinitionsMapping.get(type); - if (paramDefinition != null) { - return paramDefinition; - } else { - throw new UnsupportedOperationException("Unsupported type: " + type.getSimpleName()); - } - } - } - - static Object decode(MSSQLDataType dataType, ByteBuf in) { - switch (dataType.id()) { - case MSSQLDataTypeId.INT1TYPE_ID: - return decodeTinyInt(in); - case MSSQLDataTypeId.INT2TYPE_ID: - return decodeSmallInt(in); - case MSSQLDataTypeId.INT4TYPE_ID: - return decodeInt(in); - case MSSQLDataTypeId.INT8TYPE_ID: - return decodeBigInt(in); - case MSSQLDataTypeId.NUMERICNTYPE_ID: - case MSSQLDataTypeId.DECIMALNTYPE_ID: - return decodeDecimal((DecimalDataType) dataType, in); - case MSSQLDataTypeId.INTNTYPE_ID: - return decodeIntN(in); - case MSSQLDataTypeId.FLT4TYPE_ID: - return decodeFloat4(in); - case MSSQLDataTypeId.FLT8TYPE_ID: - return decodeFloat8(in); - case MSSQLDataTypeId.FLTNTYPE_ID: - return decodeFltN(in); - case MSSQLDataTypeId.BITTYPE_ID: - return decodeBit(in); - case MSSQLDataTypeId.BITNTYPE_ID: - return decodeBitN(in); - case MSSQLDataTypeId.DATETIMETYPE_ID: - return decodeDateTime(in); - case MSSQLDataTypeId.DATENTYPE_ID: - return decodeDateN(in); - case MSSQLDataTypeId.TIMENTYPE_ID: - return decodeTimeN((TimeNDataType) dataType, in); - case MSSQLDataTypeId.DATETIME2NTYPE_ID: - return decodeDateTime2N((DateTime2NDataType) dataType, in); - case MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID: - return decodeDateTimeOffsetN((DateTimeOffsetNDataType) dataType, in); - case MSSQLDataTypeId.BIGVARCHRTYPE_ID: - case MSSQLDataTypeId.BIGCHARTYPE_ID: - return decodeVarchar(in); - case MSSQLDataTypeId.NCHARTYPE_ID: - case MSSQLDataTypeId.NVARCHARTYPE_ID: - return decodeNVarchar(in); - case MSSQLDataTypeId.BIGBINARYTYPE_ID: - case MSSQLDataTypeId.BINARYTYPE_ID: - case MSSQLDataTypeId.BIGVARBINTYPE_ID: - case MSSQLDataTypeId.VARBINARYTYPE_ID: - return decodeBinary(in); - default: - throw new UnsupportedOperationException("Unsupported datatype: " + dataType); - } - } - - private static LocalTime decodeTimeN(TimeNDataType dataType, ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - return decodeLocalTime(in, length, dataType.scale()); - } - - private static LocalTime decodeLocalTime(ByteBuf in, int length, int scale) { - long hundredNanos; - if (length == 3) { - hundredNanos = in.readUnsignedMediumLE(); - } else if (length == 4) { - hundredNanos = in.readUnsignedIntLE(); - } else if (length == 5) { - hundredNanos = readUnsignedInt40LE(in); - } else { - throw new IllegalArgumentException("Unexpected timeLength of [" + length + "]"); - } - for (int i = scale; i < 7; i++) { - hundredNanos *= 10; - } - return LocalTime.ofNanoOfDay(100 * hundredNanos); - } - - private static LocalDateTime decodeDateTime2N(DateTime2NDataType dataType, ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - LocalTime localTime = decodeLocalTime(in, length - 3, dataType.scale()); - LocalDate localDate = decodeLocalDate(in, 3); - return LocalDateTime.of(localDate, localTime); - } - - private static OffsetDateTime decodeDateTimeOffsetN(DateTimeOffsetNDataType dataType, ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - LocalTime localTime = decodeLocalTime(in, length - 5, dataType.scale()); - LocalDate localDate = decodeLocalDate(in, 3); - short minutes = in.readShortLE(); - return LocalDateTime.of(localDate, localTime).plusMinutes(minutes).atOffset(ZoneOffset.ofTotalSeconds(60 * minutes)); - } - - private static CharSequence decodeNVarchar(ByteBuf in) { - int length = in.readUnsignedShortLE(); - if (length == 65535) { - // CHARBIN_NULL - return null; - } - return in.readCharSequence(length, StandardCharsets.UTF_16LE); - } - - private static Buffer decodeBinary(ByteBuf in) { - int length = in.readUnsignedShortLE(); - ByteBuf byteBuf = Unpooled.buffer(length); - in.readBytes(byteBuf, 0, length); - byteBuf.writerIndex(length); - return Buffer.buffer(byteBuf); - } - - private static CharSequence decodeVarchar(ByteBuf in) { - int length = in.readUnsignedShortLE(); - if (length == 65535) { - // CHARBIN_NULL - return null; - } - return in.readCharSequence(length, StandardCharsets.UTF_8); - } - - private static LocalDateTime decodeDateTime(ByteBuf in) { - LocalDate localDate = START_DATE_DATETIME.plus(in.readIntLE(), ChronoUnit.DAYS); - long nanoOfDay = NANOSECONDS.convert(Math.round(in.readIntLE() * (3 + 1D / 3)), MILLISECONDS); - LocalTime localTime = LocalTime.ofNanoOfDay(nanoOfDay); - return LocalDateTime.of(localDate, localTime); - } - - private static LocalDate decodeDateN(ByteBuf in) { - byte length = in.readByte(); - if (length == 0) { - return null; - } - return decodeLocalDate(in, length); - } - - private static LocalDate decodeLocalDate(ByteBuf in, int length) { - int days; - if (length == 3) { - days = in.readUnsignedMediumLE(); - } else { - throw new IllegalArgumentException("Unexpected dateLength of [" + length + "]"); - } - return START_DATE.plus(days, ChronoUnit.DAYS); - } - - private static boolean decodeBit(ByteBuf in) { - return in.readBoolean(); - } - - private static double decodeFloat8(ByteBuf in) { - return in.readDoubleLE(); - } - - private static float decodeFloat4(ByteBuf in) { - return in.readFloatLE(); - } - - private static BigDecimal decodeDecimal(DecimalDataType dataType, ByteBuf in) { - int scale = dataType.scale(); - short length = in.readUnsignedByte(); - if (length == 0) { - return null; - } - byte sign = in.readByte(); - byte[] bytes = new byte[length - 1]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = in.getByte(in.readerIndex() + bytes.length - 1 - i); - } - in.skipBytes(bytes.length); - BigInteger bigInteger = new BigInteger(bytes); - BigDecimal bigDecimal = new BigDecimal(bigInteger, scale); - return sign == 0 ? bigDecimal.negate() : bigDecimal; - } - - private static long decodeBigInt(ByteBuf in) { - return in.readLongLE(); - } - - private static int decodeInt(ByteBuf in) { - return in.readIntLE(); - } - - private static short decodeSmallInt(ByteBuf in) { - return in.readShortLE(); - } - - private static short decodeTinyInt(ByteBuf in) { - return in.readUnsignedByte(); - } - - private static long readUnsignedInt40LE(ByteBuf buffer) { - long low = buffer.readUnsignedIntLE(); - short high = buffer.readUnsignedByte(); - return (0x100000000L * high) + low; - } - - private static BigInteger readUnsignedInt96LE(ByteBuf buffer) { - byte[] result = new byte[12]; - int readerIndex = buffer.readerIndex(); - for (int i = 0; i < 12; i++) { - result[i] = buffer.getByte(readerIndex + 11 - i); - } - buffer.skipBytes(12); - return new BigInteger(result); - } - - private static BigInteger readUnsignedInt128LE(ByteBuf buffer) { - byte[] result = new byte[16]; - int readerIndex = buffer.readerIndex(); - for (int i = 0; i < 16; i++) { - result[i] = buffer.getByte(readerIndex + 15 - i); - } - buffer.skipBytes(16); - return new BigInteger(result); - } - - private static Object decodeIntN(ByteBuf buffer) { - int intNDataTypeLength = buffer.readByte(); - switch (intNDataTypeLength) { - case 0: - // this means we read a NULL value(nullable data type). - return null; - case 1: - return buffer.readUnsignedByte(); - case 2: - return buffer.readShortLE(); - case 4: - return buffer.readIntLE(); - case 8: - return buffer.readLongLE(); - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding IntNDataType row value.", intNDataTypeLength)); - } - } - - private static Object decodeFltN(ByteBuf buffer) { - int fltNDataTypeLength = buffer.readByte(); - switch (fltNDataTypeLength) { - case 0: - // this means we read a NULL value(nullable data type). - return null; - case 4: - return buffer.readFloatLE(); - case 8: - return buffer.readDoubleLE(); - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding FLTNTYPE row value.", fltNDataTypeLength)); - } - } - - private static Object decodeBitN(ByteBuf buffer) { - int bitNDataTypeLength = buffer.readByte(); - switch (bitNDataTypeLength) { - case 0: - // this means we read a NULL value(nullable data type). - return null; - case 1: - return buffer.readBoolean(); - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding BITNTYPE row value.", bitNDataTypeLength)); - } - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java index 7e880408a..7c9afe3ad 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/MSSQLRowDesc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * 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 @@ -22,7 +22,7 @@ class MSSQLRowDesc extends RowDesc { final ColumnData[] columnDatas; MSSQLRowDesc(ColumnData[] columnDatas) { - super(Stream.of(columnDatas).map(ColumnData::colName).collect(Collectors.toList()), Collections.unmodifiableList(Arrays.asList(columnDatas))); + super(Stream.of(columnDatas).map(ColumnData::name).collect(Collectors.toList()), Collections.unmodifiableList(Arrays.asList(columnDatas))); this.columnDatas = columnDatas; } } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java index cee4aee50..d16eb7422 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/QueryCommandBaseCodec.java @@ -12,16 +12,14 @@ package io.vertx.mssqlclient.impl.codec; import io.netty.buffer.ByteBuf; -import io.vertx.mssqlclient.impl.protocol.datatype.*; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.impl.RowDesc; import io.vertx.sqlclient.impl.command.QueryCommandBase; -import java.math.BigDecimal; import java.util.stream.Collector; import static io.vertx.mssqlclient.impl.protocol.EnvChange.*; -import static io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataTypeId.*; +import static io.vertx.mssqlclient.impl.utils.ByteBufUtils.readUnsignedByteLengthString; abstract class QueryCommandBaseCodec> extends MSSQLCommandCodec { protected RowResultDecoder rowResultDecoder; @@ -49,9 +47,10 @@ protected MSSQLRowDesc decodeColmetadataToken(ByteBuf payload) { for (int i = 0; i < columnCount; i++) { long userType = payload.readUnsignedIntLE(); int flags = payload.readUnsignedShortLE(); - MSSQLDataType dataType = decodeDataTypeMetadata(payload); - String columnName = readByteLenVarchar(payload); - columnDatas[i] = new ColumnData(userType, flags, dataType, columnName); + DataType dataType = DataType.forId(payload.readUnsignedByte()); + DataType.Metadata metadata = dataType.decodeMetadata(payload); + String columnName = readUnsignedByteLengthString(payload); + columnDatas[i] = new ColumnData(columnName, dataType, metadata); } return new MSSQLRowDesc(columnDatas); @@ -86,78 +85,6 @@ protected void handleResultSetDone(int affectedRows) { cmd.resultHandler().handleResult(affectedRows, size, rowDesc, result, failure); } - private MSSQLDataType decodeDataTypeMetadata(ByteBuf payload) { - int typeInfo = payload.readUnsignedByte(); - byte scale; - switch (typeInfo) { - /* - * FixedLen DataType - */ - case INT1TYPE_ID: - return FixedLenDataType.INT1TYPE; - case INT2TYPE_ID: - return FixedLenDataType.INT2TYPE; - case INT4TYPE_ID: - return FixedLenDataType.INT4TYPE; - case INT8TYPE_ID: - return FixedLenDataType.INT8TYPE; - case FLT4TYPE_ID: - return FixedLenDataType.FLT4TYPE; - case FLT8TYPE_ID: - return FixedLenDataType.FLT8TYPE; - case BITTYPE_ID: - return FixedLenDataType.BITTYPE; - /* - * Variable Length Data Type - */ - case NUMERICNTYPE_ID: - case DECIMALNTYPE_ID: - short decimalTypeSize = payload.readUnsignedByte(); - byte decimalPrecision = payload.readByte(); - scale = payload.readByte(); - return new DecimalDataType(typeInfo, BigDecimal.class, decimalPrecision, scale); - case INTNTYPE_ID: - byte intNTypeLength = payload.readByte(); - return IntNDataType.valueOf(intNTypeLength); - case FLTNTYPE_ID: - byte fltNTypeLength = payload.readByte(); - return FloatNDataType.valueOf(fltNTypeLength); - case BITNTYPE_ID: - payload.skipBytes(1); // should only be 1 - return BitNDataType.BIT_1_DATA_TYPE; - case DATETIMETYPE_ID: - return FixedLenDataType.DATETIMETYPE; - case DATENTYPE_ID: - return FixedLenDataType.DATENTYPE; - case TIMENTYPE_ID: - scale = payload.readByte(); - return new TimeNDataType(scale); - case DATETIME2NTYPE_ID: - scale = payload.readByte(); - return new DateTime2NDataType(scale); - case DATETIMEOFFSETNTYPE_ID: - scale = payload.readByte(); - return new DateTimeOffsetNDataType(scale); - case BIGCHARTYPE_ID: - case BIGVARCHRTYPE_ID: - case NCHARTYPE_ID: - case NVARCHARTYPE_ID: - int size = payload.readUnsignedShortLE(); - short collateCodepage = payload.readShortLE(); - short collateFlags = payload.readShortLE(); - byte collateCharsetId = payload.readByte(); - return new TextWithCollationDataType(typeInfo, String.class, null); - case BIGBINARYTYPE_ID: - case BINARYTYPE_ID: - return new BinaryDataType(typeInfo, payload.readUnsignedShortLE()); - case BIGVARBINTYPE_ID: - case VARBINARYTYPE_ID: - return new VarBinaryDataType(typeInfo, payload.readUnsignedShortLE()); - default: - throw new UnsupportedOperationException("Unsupported type with typeinfo: " + typeInfo); - } - } - void handleEnvChangeToken(ByteBuf messageBody) { int totalLength = messageBody.readUnsignedShortLE(); int startPos = messageBody.readerIndex(); diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java index 2b3f08143..9f93c46d5 100644 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/RowResultDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * 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 @@ -55,10 +55,8 @@ public void handleNbcRow(int len, ByteBuf in) { private Row decodeMssqlRow(int len, ByteBuf in) { Row row = new MSSQLRowImpl(desc); for (int c = 0; c < len; c++) { - Object decoded = null; ColumnData columnData = desc.columnDatas[c]; - decoded = MSSQLDataTypeCodec.decode(columnData.dataType(), in); - row.addValue(decoded); + row.addValue(columnData.dataType().decodeValue(in, columnData.metadata())); } return row; } @@ -78,7 +76,7 @@ private Row decodeMssqlNbcRow(int len, ByteBuf in) { if ((nullByte & mask) == 0) { // not null ColumnData columnData = desc.columnDatas[c]; - decoded = MSSQLDataTypeCodec.decode(columnData.dataType(), in); + decoded = columnData.dataType().decodeValue(in, columnData.metadata()); } row.addValue(decoded); } diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/VarBinaryDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/VarBinaryDataType.java deleted file mode 100644 index 501de462c..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/codec/VarBinaryDataType.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.mssqlclient.impl.codec; - -import io.vertx.core.buffer.Buffer; -import io.vertx.mssqlclient.impl.protocol.datatype.MSSQLDataType; - -import java.sql.JDBCType; - -public class VarBinaryDataType extends MSSQLDataType { - - private final int length; - - public VarBinaryDataType(int id, int length) { - super(id, Buffer.class, JDBCType.VARBINARY); - this.length = length; - } - - public int getLength() { - return length; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/BitNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/BitNDataType.java deleted file mode 100644 index 9414dbf1f..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/BitNDataType.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.vertx.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/** - * BITNTYPE, the only valid lengths are 0x01 for non-null instances and 0x00 for NULL instances. - */ -public class BitNDataType extends MSSQLDataType { - public static final BitNDataType BIT_1_DATA_TYPE = new BitNDataType(MSSQLDataTypeId.BITNTYPE_ID, Boolean.class, 1); - - private final int length; - - public BitNDataType(int id, Class mappedJavaType, int length) { - super(id, mappedJavaType, JDBCType.BOOLEAN); - this.length = length; - } - - public int length() { - return length; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTime2NDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTime2NDataType.java deleted file mode 100644 index 5c917ba07..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTime2NDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; -import java.time.LocalDateTime; - -public class DateTime2NDataType extends MSSQLDataType { - private final byte scale; - - public DateTime2NDataType(byte scale) { - super(MSSQLDataTypeId.DATETIME2NTYPE_ID, LocalDateTime.class, JDBCType.TIMESTAMP); - this.scale = scale; - } - - public byte scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTimeOffsetNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTimeOffsetNDataType.java deleted file mode 100644 index 9cfca5515..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DateTimeOffsetNDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; -import java.time.OffsetDateTime; - -public class DateTimeOffsetNDataType extends MSSQLDataType { - private final byte scale; - - public DateTimeOffsetNDataType(byte scale) { - super(MSSQLDataTypeId.DATETIMEOFFSETNTYPE_ID, OffsetDateTime.class, JDBCType.TIMESTAMP_WITH_TIMEZONE); - this.scale = scale; - } - - public byte scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DecimalDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DecimalDataType.java deleted file mode 100644 index de5d3bff7..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/DecimalDataType.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -// NUMERIC, NUMERICN, DECIMAL, or DECIMALN. -public class DecimalDataType extends MSSQLDataType { - private final int precision; - private final int scale; - - public DecimalDataType(int id, Class mappedJavaType, int precision, int scale) { - super(id, mappedJavaType, JDBCType.DECIMAL); - this.precision = precision; - this.scale = scale; - } - - public int precision() { - return precision; - } - - public int scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FixedLenDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FixedLenDataType.java deleted file mode 100644 index 3adf4e7da..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FixedLenDataType.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import io.vertx.core.buffer.Buffer; - -import java.sql.JDBCType; -import java.time.LocalDate; -import java.time.LocalDateTime; - -public class FixedLenDataType extends MSSQLDataType { - public FixedLenDataType(int id, Class mappedJavaType, JDBCType jdbcType) { - super(id, mappedJavaType, jdbcType); - } - - public static FixedLenDataType NULLTYPE = new FixedLenDataType(MSSQLDataTypeId.NULLTYPE_ID, null, JDBCType.OTHER); - public static FixedLenDataType INT1TYPE = new FixedLenDataType(MSSQLDataTypeId.INT1TYPE_ID, Byte.class, JDBCType.TINYINT); - public static FixedLenDataType BITTYPE = new FixedLenDataType(MSSQLDataTypeId.BITTYPE_ID, Buffer.class, JDBCType.BIT); - public static FixedLenDataType INT2TYPE = new FixedLenDataType(MSSQLDataTypeId.INT2TYPE_ID, Short.class, JDBCType.SMALLINT); - public static FixedLenDataType INT4TYPE = new FixedLenDataType(MSSQLDataTypeId.INT4TYPE_ID, Integer.class, JDBCType.INTEGER); - public static FixedLenDataType DATETIM4TYPE = new FixedLenDataType(MSSQLDataTypeId.DATETIM4TYPE_ID, LocalDateTime.class, JDBCType.TIMESTAMP); - public static FixedLenDataType FLT4TYPE = new FixedLenDataType(MSSQLDataTypeId.FLT4TYPE_ID, Float.class, JDBCType.REAL); - public static FixedLenDataType MONEYTYPE = new FixedLenDataType(MSSQLDataTypeId.MONEYTYPE_ID, null, JDBCType.OTHER); //TODO - public static FixedLenDataType DATETIMETYPE = new FixedLenDataType(MSSQLDataTypeId.DATETIMETYPE_ID, LocalDateTime.class, JDBCType.TIMESTAMP); - public static FixedLenDataType FLT8TYPE = new FixedLenDataType(MSSQLDataTypeId.FLT8TYPE_ID, Double.class, JDBCType.DOUBLE); - public static FixedLenDataType MONEY4TYPE = new FixedLenDataType(MSSQLDataTypeId.MONEY4TYPE_ID, null, JDBCType.OTHER); //TODO - public static FixedLenDataType INT8TYPE = new FixedLenDataType(MSSQLDataTypeId.INT8TYPE_ID, Long.class, JDBCType.BIGINT); - - // DATENTYPE 0 or 3 length - public static FixedLenDataType DATENTYPE = new FixedLenDataType(MSSQLDataTypeId.DATENTYPE_ID, LocalDate.class, JDBCType.DATE); -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FloatNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FloatNDataType.java deleted file mode 100644 index 91c6aac3d..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/FloatNDataType.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.vertx.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/** - * FLTNTYPE, Variable-Length Data type, the only valid lengths are 0x04 and 0x08, which map to 7-digit precision float and 15-digit precision float SQL data types respectively. - */ -public class FloatNDataType extends MSSQLDataType { - public static final FloatNDataType FLT_4_DATA_TYPE = new FloatNDataType(MSSQLDataTypeId.FLTNTYPE_ID, Float.class, 4, JDBCType.REAL); - public static final FloatNDataType FLT_8_DATA_TYPE = new FloatNDataType(MSSQLDataTypeId.FLTNTYPE_ID, Double.class, 8, JDBCType.DOUBLE); - - private final int length; - - public FloatNDataType(int id, Class mappedJavaType, int length, JDBCType jdbcType) { - super(id, mappedJavaType, jdbcType); - this.length = length; - } - - public int length() { - return length; - } - - public static FloatNDataType valueOf(int length) { - switch (length) { - case 4: - return FLT_4_DATA_TYPE; - case 8: - return FLT_8_DATA_TYPE; - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding FLTNTYPE column metadata.", length)); - } - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/IntNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/IntNDataType.java deleted file mode 100644 index 4d06492c5..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/IntNDataType.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2011-2020 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/** - * Variable-Length Data type, length may be 0x01, 0x02, 0x04, and 0x08. - */ -public class IntNDataType extends MSSQLDataType { - public static final IntNDataType INT_1_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Byte.class, 1, JDBCType.TINYINT); - public static final IntNDataType INT_2_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Short.class, 2, JDBCType.SMALLINT); - public static final IntNDataType INT_4_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Integer.class, 4, JDBCType.INTEGER); - public static final IntNDataType INT_8_DATA_TYPE = new IntNDataType(MSSQLDataTypeId.INTNTYPE_ID, Long.class, 8, JDBCType.BIGINT); - - private final int length; - - private IntNDataType(int id, Class mappedJavaType, int length, JDBCType jdbcType) { - super(id, mappedJavaType, jdbcType); - this.length = length; - } - - public int length() { - return length; - } - - public static IntNDataType valueOf(int length) { - switch (length) { - case 1: - return INT_1_DATA_TYPE; - case 2: - return INT_2_DATA_TYPE; - case 4: - return INT_4_DATA_TYPE; - case 8: - return INT_8_DATA_TYPE; - default: - throw new UnsupportedOperationException(String.format("SEVERE: Unsupported length=[%d] for decoding IntNDataType column metadata.", length)); - } - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataType.java deleted file mode 100644 index 0cb582ee6..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataType.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -/* - DATE MUST NOT have a TYPE_VARLEN. - The value is either 3 bytes or 0 bytes (null). - TIME, DATETIME2, and DATETIMEOFFSET MUST NOT have a TYPE_VARLEN. The lengths are determined by the SCALE as indicated in section 2.2.5.4.2. - PRECISION and SCALE MUST occur if the type is NUMERIC, NUMERICN, DECIMAL, or DECIMALN. - SCALE (without PRECISION) MUST occur if the type is TIME, DATETIME2, or DATETIMEOFFSET (introduced in TDS 7.3). PRECISION MUST be less than or equal to decimal 38 and SCALE MUST be less than or equal to the precision value. - COLLATION occurs only if the type is BIGCHARTYPE, BIGVARCHRTYPE, TEXTTYPE, NTEXTTYPE, NCHARTYPE, or NVARCHARTYPE. - UDT_INFO always occurs if the type is UDTTYPE. - XML_INFO always occurs if the type is XMLTYPE. - USHORTMAXLEN does not occur if PARTLENTYPE is XMLTYPE or UDTTYPE. - */ -public abstract class MSSQLDataType { - protected final int id; - protected final Class mappedJavaType; - protected final JDBCType jdbcType; - - public MSSQLDataType(int id, Class mappedJavaType, JDBCType jdbcType) { - this.id = id; - this.mappedJavaType = mappedJavaType; - this.jdbcType = jdbcType; - } - - public int id() { - return id; - } - - public Class mappedJavaType() { - return mappedJavaType; - } - - public JDBCType jdbcType() { - return jdbcType; - } - -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataTypeId.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataTypeId.java deleted file mode 100644 index d3bf16c0d..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/MSSQLDataTypeId.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -public final class MSSQLDataTypeId { - /* - Fixed-Length Data Types - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/859eb3d2-80d3-40f6-a637-414552c9c552 - */ - public static final int NULLTYPE_ID = 0x1F; - public static final int INT1TYPE_ID = 0x30; - public static final int BITTYPE_ID = 0x32; - public static final int INT2TYPE_ID = 0x34; - public static final int INT4TYPE_ID = 0x38; - public static final int DATETIM4TYPE_ID = 0x3A; - public static final int FLT4TYPE_ID = 0x3B; - public static final int MONEYTYPE_ID = 0x3C; - public static final int DATETIMETYPE_ID = 0x3D; - public static final int FLT8TYPE_ID = 0x3E; - public static final int MONEY4TYPE_ID = 0x7A; - public static final int INT8TYPE_ID = 0x7F; - - /* - Variable-Length Data Types - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/ce3183a6-9d89-47e8-a02f-de5a1a1303de - */ - public static final int GUIDTYPE_ID = 0x24; - public static final int INTNTYPE_ID = 0x26; - public static final int DECIMALTYPE_ID = 0x37; - public static final int NUMERICTYPE_ID = 0x3F; - public static final int BITNTYPE_ID = 0x68; - public static final int DECIMALNTYPE_ID = 0x6A; - public static final int NUMERICNTYPE_ID = 0x6C; - public static final int FLTNTYPE_ID = 0x6D; - public static final int MONEYNTYPE_ID = 0x6E; - public static final int DATETIMNTYPE_ID = 0x6F; - public static final int DATENTYPE_ID = 0x28; - public static final int TIMENTYPE_ID = 0x29; - public static final int DATETIME2NTYPE_ID = 0x2A; - public static final int DATETIMEOFFSETNTYPE_ID = 0x2B; - public static final int CHARTYPE_ID = 0x2F; - public static final int VARCHARTYPE_ID = 0x27; - public static final int BINARYTYPE_ID = 0x2D; - public static final int VARBINARYTYPE_ID = 0x25; - public static final int BIGVARBINTYPE_ID = 0xA5; - public static final int BIGVARCHRTYPE_ID = 0xA7; - public static final int BIGBINARYTYPE_ID = 0xAD; - public static final int BIGCHARTYPE_ID = 0xAF; - public static final int NVARCHARTYPE_ID = 0xE7; - public static final int NCHARTYPE_ID = 0xEF; - public static final int XMLTYPE_ID = 0xF1; - public static final int UDTTYPE_ID = 0xF0; - public static final int TEXTTYPE_ID = 0x23; - public static final int IMAGETYPE_ID = 0x22; - public static final int NTEXTTYPE_ID = 0x63; - public static final int SSVARIANTTYPE_ID = 0x62; -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TextWithCollationDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TextWithCollationDataType.java deleted file mode 100644 index 89e694785..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TextWithCollationDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; - -// BIGCHARTYPE, BIGVARCHRTYPE, TEXTTYPE, NTEXTTYPE, NCHARTYPE, or NVARCHARTYPE -public class TextWithCollationDataType extends MSSQLDataType { - private final String collation; - - public TextWithCollationDataType(int id, Class mappedJavaType, String collation) { - super(id, mappedJavaType, JDBCType.VARCHAR); - this.collation = collation; - } - - public String collation() { - return collation; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TimeNDataType.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TimeNDataType.java deleted file mode 100644 index fabb0d6b4..000000000 --- a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/protocol/datatype/TimeNDataType.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2019 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.mssqlclient.impl.protocol.datatype; - -import java.sql.JDBCType; -import java.time.LocalTime; - -public class TimeNDataType extends MSSQLDataType { - private byte scale; - - public TimeNDataType(byte scale) { - super(MSSQLDataTypeId.TIMENTYPE_ID, LocalTime.class, JDBCType.TIME); - this.scale = scale; - } - - public byte scale() { - return scale; - } -} diff --git a/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/utils/ByteBufUtils.java b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/utils/ByteBufUtils.java new file mode 100644 index 000000000..c29fd65e2 --- /dev/null +++ b/vertx-mssql-client/src/main/java/io/vertx/mssqlclient/impl/utils/ByteBufUtils.java @@ -0,0 +1,58 @@ +/* + * 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.mssqlclient.impl.utils; + +import io.netty.buffer.ByteBuf; + +import static java.nio.charset.StandardCharsets.UTF_16LE; + +public class ByteBufUtils { + + public static void writeByteLengthString(ByteBuf buffer, String value) { + if (value == null) { + buffer.writeByte(0); + } else { + buffer.writeByte(value.length()); + buffer.writeCharSequence(value, UTF_16LE); + } + } + + public static String readUnsignedByteLengthString(ByteBuf buffer) { + int length = buffer.readUnsignedByte(); + return buffer.readCharSequence(length * 2, UTF_16LE).toString(); + } + + public static String readUnsignedShortLengthString(ByteBuf buffer) { + int length = buffer.readUnsignedShortLE(); + return buffer.readCharSequence(length * 2, UTF_16LE).toString(); + } + + public static void writeUnsignedShortLengthString(ByteBuf buffer, String value) { + buffer.writeShortLE(value.length() * 2); + buffer.writeCharSequence(value, UTF_16LE); + } + + public static long readUnsignedInt40LE(ByteBuf buffer) { + long low = buffer.readUnsignedIntLE(); + short high = buffer.readUnsignedByte(); + return (0x100000000L * high) + low; + } + + public static void writeUnsignedInt40LE(ByteBuf buffer, long value) { + buffer.writeIntLE((int) (value % 0x100000000L)); + buffer.writeByte((int) (value / 0x100000000L)); + } + + private ByteBufUtils() { + // Utility + } +}